Repository: prebid/prebid-server
Pull request #2170 (2022-03-02 07:32:18)

Use exchange package's Exchange implementation in JSON tests
The current version of prebid server uses a `mockBidExchange` object in order to run the `TestJsonSampleRequests` test found in `endpoints/openrtb2/auction_test.go`. This is a very comprehensive test that reads input and expected data from the JSON files found inside the `endpoints/openrtb2/sample-requests` directory. This pull request removes `mockBidExchange` and uses the `exchange/` directory's implementation of an `exchange` instead. To do so, this PR:
  1. Exports the `AdaptedBidder` interface defined inside `exchange/bidder.go` in order to build an adapter map inside `endpoints/openrtb2/auction_test.go` whose elements are mock bidder adapters pointing to mock bidder servers.
  2. Makes the appropriate changes inside `endpoints/openrtb2/auction_test.go` to drop `mockBidExchange` and use an `exchange` implementation.
  3. Incorporates file `endpoints/openrtb2/test_utils.go` in order to break the enormous `endpoints/openrtb2/auction_test.go` file into two. `endpoints/openrtb2/test_utils.go` was written to carry auxiliary types, functions and interface implementations useful for the testing framework.
  4. Tweaks JSON files where needed. For instance, the following SON test files were modified to expect warnings instead:
  ```
endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json
endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json
endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json
endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json
endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json
  ```
  5. Defines `BenchmarkAllExtRequest` inside `endpoints/openrtb2/auction_benchmark_test.go` and corrects a bug in `BenchmarkOpenrtbEndpoint` also found in said file.
  
Please let me know your takes.


VeronikaSolovei9: Overall looks good to me, simpler and clearer. 
I have some questions to make sure I understand it all right.

1) With new test exchange there are will be 3 predefined bidders for all exchange tests: appnexus, openx and rubicon, right? 
2) Benchmark tests: are they the part of existing tests or they should be run separately to check memory/cpu usage is still ok after some new changes? 
3) Why existing tests started to throw a warning? 
guscarreon:  > 1) With new test exchange there are will be 3 predefined bidders for all exchange tests: appnexus, openx and rubicon, right?

Right I didn't want to create a new bidder simply for testing purposes so these tests simply mock these bidders' bid servers. Do you agree with this approach?

 > 2) Benchmark tests: are they the part of existing tests or should they be run separately to check memory/cpu usage is still ok after some new changes?

They run separately with the `-bench` flag of the `go test` command. 

 > 3) Why existing tests started to throw a warning?

Any warning in particular? The `HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog)` implementation found in the `exchange` package builds a `bidResponse.Ext` with the contract found in the  `openrtb_ext` package:
```
178 func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) {
179     var err error
180     requestExt, err := extractBidRequestExt(r.BidRequest)
181 *-- 51 lines: if err != nil {--------------------------------------------------------------------------------------------
232
233     var auc *auction
234     var cacheErrs []error
235     var bidResponseExt *openrtb_ext.ExtBidResponse
236     if anyBidsReturned {
237 *-- 87 lines: var bidCategory map[string]string--------------------------------------------------------------------------
324     // Build the response
325     return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, errs)
326 }
exchange/exchange.go
```
Does this answer your question?
VeronikaSolovei9: Overall LGTM. Would be good to have another pair of eyes on it to make sure I didn't miss anything
AlexBVolcy: Just want to point out that a test is failing in `TestGoodAmpRequests` for what looks like a seg fault
guscarreon: > Just want to point out that a test is failing in `TestGoodAmpRequests` for what looks like a seg fault

It's been corrected Alex
VeronikaSolovei9: Gus, what you implemented for amp endpoint testing is impressive! 
I want to confirm couple of things. 
For `TestGoodAmpRequests` you specify 7 test cases. 6 of them have `mockBidRequest.imp.ext.prebid.storedauctionresponse` which means that some of exchange logic will not be executed: `e.getAllBids`, exchange.go, L274 for majority of test cases. Instead `buildStoredAuctionResponse` L265 will be executed. Is this the intention? Or should only `site-stored-response.json` test execute `buildStoredAuctionResponse`? 
To be clear, I'm ok with this, just want to make sure this is how it should be. The only test that executes `getAllBids` is `gdpr.json`. This file still have `mockAmpStoredResponse` despite it's not needed for this test (it works without it, I tried :)). 

Patch:
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 9a7ae430..210dbdb5 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -9,7 +9,7 @@
 			"VARIANT": "1.16",
 			// Options
 			"INSTALL_NODE": "false",
-			"NODE_VERSION": "lts/*",
+			"NODE_VERSION": "lts/*"
 		}
 	},
 	"containerEnv": {
diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go
index 98264ce3..37ff6191 100644
--- a/adapters/sovrn/sovrn.go
+++ b/adapters/sovrn/sovrn.go
@@ -21,28 +21,6 @@ type SovrnAdapter struct {
 }
 
 func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
-	errs := make([]error, 0, len(request.Imp))
-
-	for i := 0; i < len(request.Imp); i++ {
-		_, err := preprocess(&request.Imp[i])
-		if err != nil {
-			errs = append(errs, err)
-			request.Imp = append(request.Imp[:i], request.Imp[i+1:]...)
-			i--
-		}
-	}
-
-	// If all the requests were malformed, don't bother making a server call with no impressions.
-	if len(request.Imp) == 0 {
-		return nil, errs
-	}
-
-	reqJSON, err := json.Marshal(request)
-	if err != nil {
-		errs = append(errs, err)
-		return nil, errs
-	}
-
 	headers := http.Header{}
 	headers.Add("Content-Type", "application/json")
 	if request.Device != nil {
@@ -61,6 +39,70 @@ func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt
 		}
 	}
 
+	errs := make([]error, 0, len(request.Imp))
+	var err error
+	validImps := make([]openrtb2.Imp, 0, len(request.Imp))
+
+	for _, imp := range request.Imp {
+		var bidderExt adapters.ExtImpBidder
+		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+			errs = append(errs, &errortypes.BadInput{
+				Message: err.Error(),
+			})
+			continue
+		}
+
+		var sovrnExt openrtb_ext.ExtImpSovrn
+		if err := json.Unmarshal(bidderExt.Bidder, &sovrnExt); err != nil {
+			errs = append(errs, &errortypes.BadInput{
+				Message: err.Error(),
+			})
+			continue
+		}
+
+		tagId := getTagId(sovrnExt)
+		if tagId == "" {
+			errs = append(errs, &errortypes.BadInput{
+				Message: "Missing required parameter 'tagid'",
+			})
+			continue
+		}
+
+		imp.TagID = tagId
+
+		if imp.BidFloor == 0 && sovrnExt.BidFloor > 0 {
+			imp.BidFloor = sovrnExt.BidFloor
+		}
+
+		// Validate video params if appropriate
+		video := imp.Video
+		if video != nil {
+			if video.MIMEs == nil ||
+				video.MinDuration == 0 ||
+				video.MaxDuration == 0 ||
+				video.Protocols == nil {
+				errs = append(errs, &errortypes.BadInput{
+					Message: "Missing required video parameter",
+				})
+				continue
+			}
+		}
+
+		validImps = append(validImps, imp)
+	}
+
+	if len(validImps) == 0 {
+		return nil, errs
+	}
+
+	request.Imp = validImps
+
+	reqJSON, err := json.Marshal(request)
+	if err != nil {
+		errs = append(errs, err)
+		return nil, errs
+	}
+
 	return []*adapters.RequestData{{
 		Method:  "POST",
 		Uri:     s.URI,
@@ -75,78 +117,77 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str
 	}
 }
 
-func (s *SovrnAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
-	if response.StatusCode == http.StatusNoContent {
+func (s *SovrnAdapter) MakeBids(request *openrtb2.BidRequest, bidderRequest *adapters.RequestData, bidderResponse *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if bidderResponse.StatusCode == http.StatusNoContent {
 		return nil, nil
 	}
 
-	if response.StatusCode == http.StatusBadRequest {
+	if bidderResponse.StatusCode == http.StatusBadRequest {
 		return nil, []error{&errortypes.BadInput{
-			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", bidderResponse.StatusCode),
 		}}
 	}
 
-	if response.StatusCode != http.StatusOK {
+	if bidderResponse.StatusCode != http.StatusOK {
 		return nil, []error{&errortypes.BadServerResponse{
-			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", bidderResponse.StatusCode),
 		}}
 	}
 
-	var bidResp openrtb2.BidResponse
-	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+	var bidResponse openrtb2.BidResponse
+	if err := json.Unmarshal(bidderResponse.Body, &bidResponse); err != nil {
 		return nil, []error{&errortypes.BadServerResponse{
 			Message: err.Error(),
 		}}
 	}
 
-	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+	response := adapters.NewBidderResponseWithBidsCapacity(5)
+	errs := make([]error, 0)
 
-	for _, sb := range bidResp.SeatBid {
-		for i := 0; i < len(sb.Bid); i++ {
-			bid := sb.Bid[i]
+	for _, sb := range bidResponse.SeatBid {
+		for _, bid := range sb.Bid {
 			adm, err := url.QueryUnescape(bid.AdM)
 			if err == nil {
 				bid.AdM = adm
-				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+
+				bidType := openrtb_ext.BidTypeBanner
+
+				impIdx, impIdErr := getImpIdx(bid.ImpID, request)
+				if impIdErr != nil {
+					errs = append(errs, impIdErr)
+					continue
+				} else if request.Imp[impIdx].Video != nil {
+					bidType = openrtb_ext.BidTypeVideo
+				}
+
+				response.Bids = append(response.Bids, &adapters.TypedBid{
 					Bid:     &bid,
-					BidType: openrtb_ext.BidTypeBanner,
+					BidType: bidType,
 				})
 			}
 		}
 	}
 
-	return bidResponse, nil
+	return response, errs
 }
 
-func preprocess(imp *openrtb2.Imp) (string, error) {
-	var bidderExt adapters.ExtImpBidder
-	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
-		return "", &errortypes.BadInput{
-			Message: err.Error(),
-		}
+func getTagId(sovrnExt openrtb_ext.ExtImpSovrn) string {
+	if len(sovrnExt.Tagid) > 0 {
+		return sovrnExt.Tagid
+	} else {
+		return sovrnExt.TagId
 	}
+}
 
-	var sovrnExt openrtb_ext.ExtImpSovrn
-	if err := json.Unmarshal(bidderExt.Bidder, &sovrnExt); err != nil {
-		return "", &errortypes.BadInput{
-			Message: err.Error(),
+func getImpIdx(impId string, request *openrtb2.BidRequest) (int, error) {
+	for idx, imp := range request.Imp {
+		if imp.ID == impId {
+			return idx, nil
 		}
 	}
 
-	imp.TagID = getTagid(sovrnExt)
-
-	if imp.BidFloor == 0 && sovrnExt.BidFloor > 0 {
-		imp.BidFloor = sovrnExt.BidFloor
-	}
-
-	return imp.TagID, nil
-}
-
-func getTagid(sovrnExt openrtb_ext.ExtImpSovrn) string {
-	if len(sovrnExt.Tagid) > 0 {
-		return sovrnExt.Tagid
-	} else {
-		return sovrnExt.TagId
+	return -1, &errortypes.BadInput{
+		Message: fmt.Sprintf("Imp ID %s in bid didn't match with any imp in the original request", impId),
 	}
 }
 
diff --git a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json
index 75c859d2..8fb028a6 100644
--- a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json
+++ b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json
@@ -73,11 +73,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 0
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json
index ae930384..57327d49 100644
--- a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json
+++ b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json
@@ -75,11 +75,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 0
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json
index 00906c89..8ad865f5 100644
--- a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json
+++ b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json
@@ -1,7 +1,11 @@
 {
   "description": "Imp extension comes with a valid bidder name and valid bidder aliases as defined in the config.aliases list. Given that 'alias1' refers to the 'appnexus' bidder, we only bid appnexus once.",
   "config": {
-      "aliases": "{\"ext\":{\"prebid\":{\"aliases\":{\"alias1\":\"appnexus\",\"alias2\":\"rubicon\"}}}}"
+    "aliases": "{\"ext\":{\"prebid\":{\"aliases\":{\"alias1\":\"appnexus\",\"alias2\":\"rubicon\"}}}}",
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 0.00},
+      {"bidderName": "rubicon", "currency": "USD", "price": 0.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -58,32 +62,32 @@
       {
         "bid": [
           {
-            "id": "alias1-bid",
-            "impid": "",
+            "id": "appnexus-bid",
+            "impid": "my-imp-id",
             "price": 0
           }
         ],
-        "seat": "alias1-bids"
+        "seat": "alias1"
       },
       {
         "bid": [
           {
-            "id": "alias2-bid",
-            "impid": "",
+            "id": "rubicon-bid",
+            "impid": "my-imp-id",
             "price": 0
           }
         ],
-        "seat": "alias2-bids"
+        "seat": "alias2"
       },
       {
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "my-imp-id",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ],
     "bidid": "test bid id",
diff --git a/endpoints/openrtb2/sample-requests/aliased/simple.json b/endpoints/openrtb2/sample-requests/aliased/simple.json
index 677d3d8c..6e1440a7 100644
--- a/endpoints/openrtb2/sample-requests/aliased/simple.json
+++ b/endpoints/openrtb2/sample-requests/aliased/simple.json
@@ -33,12 +33,12 @@
       {
         "bid": [
           {
-            "id": "alias1-bid",
-            "impid": "",
+            "id": "appnexus-bid",
+            "impid": "my-imp-id",
             "price": 0
           }
         ],
-       "seat": "alias1-bids"
+       "seat": "alias1"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json
index 03877031..154c36ef 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json
@@ -6,10 +6,13 @@
         "MXN": 2.00
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 1.00
+      }
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -41,6 +44,32 @@
       }
     }
   },
-  "expectedReturnCode": 500,
-  "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "ext": {
+      "errors": {
+        "appnexus": [
+          {
+            "code": 999,
+            "message": "Currency conversion rate not found: 'USD' => 'MXN'"
+          }
+        ]
+      },
+      "warnings": {
+        "general": [
+          {
+            "code": 10002,
+            "message": "debug turned off for account"
+          }
+        ]
+      },
+      "responsetimemillis": {
+        "appnexus": 0
+      },
+      "prebid": {
+        "auctiontimestamp": 1643437745488
+      }
+    }
+  },
+  "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json
index 6a727e96..3217dd1e 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json
@@ -6,10 +6,13 @@
         "MXN": 2.00
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 1.00
+      }
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -47,6 +50,32 @@
       }
     }
   },
-  "expectedReturnCode": 500,
-  "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "ext": {
+      "errors": {
+        "appnexus": [
+          {
+            "code": 999,
+            "message": "Currency conversion rate not found: 'USD' => 'GBP'"
+          }
+        ]
+      },
+      "warnings": {
+        "general": [
+          {
+            "code": 10002,
+            "message": "debug turned off for account"
+          }
+        ]
+      },
+      "responsetimemillis": {
+        "appnexus": 0
+      },
+      "prebid": {
+        "auctiontimestamp": 1643437745495
+      }
+    }
+  },
+  "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json
index 5549fa9b..a9e1b184 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json
@@ -6,10 +6,9 @@
         "MXN": 5.09
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -48,6 +47,32 @@
       }
     }
   },
-  "expectedReturnCode": 500,
-  "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "ext": {
+      "errors": {
+        "appnexus": [
+          {
+            "code": 999,
+            "message": "Currency conversion rate not found: 'USD' => 'MXN'"
+          }
+        ]
+      },
+      "warnings": {
+        "general": [
+          {
+            "code": 10002,
+            "message": "debug turned off for account"
+          }
+        ]
+      },
+      "responsetimemillis": {
+        "appnexus": 0
+      },
+      "prebid": {
+        "auctiontimestamp": 1643437745503
+      }
+    }
+  },
+  "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json
index f4e19f3a..52e25edf 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json
@@ -6,10 +6,9 @@
         "MXN": 5.09
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -44,6 +43,32 @@
       }
     }
   },
-  "expectedReturnCode": 500,
-  "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "ext": {
+      "errors": {
+        "appnexus": [
+          {
+            "code": 999,
+            "message": "Currency conversion rate not found: 'USD' => 'MXN'"
+          }
+        ]
+      },
+      "warnings": {
+        "general": [
+          {
+            "code": 10002,
+            "message": "debug turned off for account"
+          }
+        ]
+      },
+      "responsetimemillis": {
+        "appnexus": 0
+      },
+      "prebid": {
+        "auctiontimestamp": 1643437745510
+      }
+    }
+  },
+  "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json
index 39857650..3322f181 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json
@@ -6,10 +6,9 @@
         "MXN": 5.09
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json
index 0741ea4d..bb1162ae 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json
@@ -6,10 +6,9 @@
         "MXN": 2.00
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -50,11 +49,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 1.00
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json
index fb65a852..5ba56dfc 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json
@@ -6,10 +6,9 @@
         "MXN": 2.00
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -58,11 +57,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 2.00
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json
index 80790a52..b8eb34a0 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json
@@ -6,10 +6,9 @@
         "MXN": 2.00
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -57,11 +56,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 5.00
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json
index ef372c1c..e038e2bf 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json
@@ -6,10 +6,9 @@
         "MXN": 2.00
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -58,11 +57,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 5.00
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json
index 276e8da4..bbb8f513 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json
@@ -6,10 +6,9 @@
         "MXN": 2.00
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -58,11 +57,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 5.00
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json
index 624f0784..bb85307a 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json
@@ -6,10 +6,9 @@
         "MXN": 8.00
       }
     },
-    "mockBidder": {
-      "currency": "MXN",
-	  "price": 20.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "MXN", "price": 20.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -54,11 +53,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 2
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json
index 929c2e0c..5e42cc2f 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json
@@ -6,10 +6,9 @@
         "MXN": 2.00
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -58,11 +57,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 2.00
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json
index dc0d7ce6..1e40126e 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json
@@ -6,10 +6,9 @@
         "GBP": 0.80
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -33,6 +32,32 @@
       }
     ]
   },
-  "expectedReturnCode": 500,
-  "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to."
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "ext": {
+      "errors": {
+        "appnexus": [
+          {
+            "code": 999,
+            "message": "Currency conversion rate not found: 'USD' => 'MXN'"
+          }
+        ]
+      },
+      "warnings": {
+        "general": [
+          {
+            "code": 10002,
+            "message": "debug turned off for account"
+          }
+        ]
+      },
+      "responsetimemillis": {
+        "appnexus": 0
+      },
+      "prebid": {
+        "auctiontimestamp": 1643437745429
+      }
+    }
+  },
+  "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json
index 84788d5a..74000015 100644
--- a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json
+++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json
@@ -6,10 +6,9 @@
         "MXN": 2.00
       }
     },
-    "mockBidder": {
-      "currency": "USD",
-	  "price": 1.00
-    }
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -43,11 +42,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "my-imp-id",
               "price": 2.00
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/disabled/good/partial.json b/endpoints/openrtb2/sample-requests/disabled/good/partial.json
index 735e7c5e..01b6177f 100644
--- a/endpoints/openrtb2/sample-requests/disabled/good/partial.json
+++ b/endpoints/openrtb2/sample-requests/disabled/good/partial.json
@@ -1,7 +1,12 @@
 {
   "description": "Request comes with some imps directed toward disabled adapters, but there's one non-disabled adapter and we expect a successful response",
   "config": {
-    "disabledAdapters": ["appnexus", "rubicon"]
+    "disabledAdapters": ["appnexus", "rubicon"],
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00},
+      {"bidderName": "rubicon", "currency": "USD", "price": 1.00},
+      {"bidderName": "openx", "currency": "USD", "price": 1.00}
+    ]
   },
   "mockBidRequest": {
     "id": "some-request-id",
@@ -65,11 +70,11 @@
           "bid": [
             {
               "id": "openx-bid",
-              "impid": "",
-              "price": 0
+              "impid": "my-imp-id",
+              "price": 1
             }
           ],
-          "seat": "openx-bids"
+          "seat": "openx"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json
index d9ed59c8..1363eefb 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json
@@ -1,5 +1,8 @@
 {
   "description": "Bid request's sole imp element has invalid ext value",
+  "config": {
+    "realParamsValidator": true
+  },
   "mockBidRequest": {
     "id": "req-id",
     "imp": [
@@ -8,7 +11,7 @@
         "audio": {
           "mimes": [
             "video/mp4"
-            ]
+          ]
         },
         "ext": {
           "appnexus": "invalidParams"
diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json
index e556b15d..18e5623e 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json
index 06673bcd..5701ad57 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json
index 9b876349..957d1572 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json
@@ -30,11 +30,11 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
+              "impid": "some-imp",
               "price": 0
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         }
       ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json
index 22ffc7f5..51bdd5a9 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json
index e60e2028..2b2376c3 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json
index a3b7101d..1be29c95 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json
index 77e8ce10..52d71454 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json
index 21403117..2e8a4e47 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json
@@ -26,10 +26,10 @@
         "seatbid": [{
             "bid": [{
                 "id": "appnexus-bid",
-                "impid": "",
+                "impid": "some-imp",
                 "price": 0
             }],
-            "seat": "appnexus-bids"
+            "seat": "appnexus"
         }]
     },
     "expectedReturnCode": 200
diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json
index 5ebc4e69..45d2b9fe 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json
index 5518b7a0..9475b778 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json
index fcc7b72d..763135b1 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json
index f920c52a..f86d53a7 100644
--- a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json
+++ b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json
@@ -30,11 +30,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-imp",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ]
   },
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json
index fb29efee..8f64fd2a 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json
@@ -1,5 +1,11 @@
 {
   "description": "This demonstrates all of the OpenRTB extensions supported by Prebid Server. Very few requests will need all of these at once.",
+  "config": {
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 1.00},
+      {"bidderName": "rubicon", "currency": "USD", "price": 1.00}
+    ]
+  },
   "mockBidRequest": {
     "id": "some-request-id",
     "site": {
@@ -91,31 +97,31 @@
           "bid": [
             {
               "id": "appnexus-bid",
-              "impid": "",
-              "price": 0
+              "impid": "some-impression-id",
+              "price": 1.01
             }
           ],
-          "seat": "appnexus-bids"
+          "seat": "appnexus"
         },
         {
           "bid": [
             {
-              "id": "districtm-bid",
-              "impid": "",
-              "price": 0
+              "id": "appnexus-bid",
+              "impid": "some-impression-id",
+              "price": 0.98
             }
           ],
-          "seat": "districtm-bids"
+          "seat": "districtm"
         },
         {
           "bid": [
             {
               "id": "rubicon-bid",
-              "impid": "",
-              "price": 0
+              "impid": "some-impression-id",
+              "price": 0.99
             }
           ],
-          "seat": "rubicon-bids"
+          "seat": "rubicon"
         }
       ],
       "bidid":"test bid id",
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/interstitial-no-size.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/interstitial-no-size.json
index 6100769a..195cd150 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/interstitial-no-size.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/interstitial-no-size.json
@@ -25,10 +25,10 @@
         "seatbid": [{
             "bid": [{
                 "id": "appnexus-bid",
-                "impid": "",
+                "impid": "some-imp-id",
                 "price": 0
             }],
-            "seat": "appnexus-bids"
+            "seat": "appnexus"
         }],
         "bidid": "test bid id",
         "cur": "USD",
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json
new file mode 100644
index 00000000..eb9c8ee8
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/simple.json
@@ -0,0 +1,58 @@
+{
+  "description": "Simple request",
+  "config": {
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 0.00}
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ],
+    "tmax": 500,
+    "ext": {}
+  },
+  "expectedBidResponse": {
+      "id":"some-request-id",
+      "seatbid": [
+        {
+          "bid": [
+            {
+              "id": "appnexus-bid",
+              "impid": "some-impression-id",
+              "price": 0,
+              "ext": {"prebid":{"type":"banner"}}
+            }
+          ],
+          "seat": "appnexus"
+        }
+      ],
+      "bidid":"test bid id",
+      "cur":"USD",
+      "nbr":0
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json
index cb2cec99..24d70ff9 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json
@@ -36,10 +36,10 @@
         "seatbid": [{
             "bid": [{
                 "id": "appnexus-bid",
-                "impid": "",
+                "impid": "some-imp-id",
                 "price": 0
             }],
-            "seat": "appnexus-bids"
+            "seat": "appnexus"
         }],
         "bidid": "test bid id",
         "cur":"USD",
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliased-buyeruids.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliased-buyeruids.json
index 141b643a..bf0f7a04 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliased-buyeruids.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliased-buyeruids.json
@@ -1,49 +1,63 @@
 {
-  "description": "Well formed amp request that comes with user field and buyeruids values",
+  "description": "Amp request where root.user.ext.prebid.buyeruids field makes use of alias defined in root.ext.prebid.aliases",
+  "config": {
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 2}
+    ]
+  },
   "mockBidRequest": {
-      "id": "request-without-user-ext-obj",
-      "site": {
-          "page": "test.somepage.com"
-        },
-      "imp": [
-            {
-              "id": "my-imp-id",
-              "banner": {
-                  "format": [
-                        {
-                          "w": 300,
-                          "h": 600
-                        }
-                    ]
-                },
-              "ext": {
-                  "appnexus": {
-                      "placementId": 12883451
-                    }
-                }
-            }
-        ],
-      "user": {
-          "ext": {
-              "prebid": {
-                  "buyeruids": {
-                      "unknown": "123"
-                    }
-                }
-            }
+    "id": "request-with-user-ext-obj",
+    "site": {"page": "test.somepage.com"},
+    "imp": [
+      {
+        "id": "my-imp-id",
+        "banner": {
+          "format": [
+            {"w": 300, "h": 600}
+          ]
         },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        }
+      }
+    ],
+    "user": {
       "ext": {
-          "prebid": {
-              "aliases": {
-                  "unknown": "appnexus"
-                }
-            }
+        "prebid": {
+          "buyeruids": {
+            "unknown": "123"
+          }
         }
+      }
     },
-  "expectedBidResponse": {
-      "id":"request-without-user-ext-obj",
-      "bidid":"test bid id",
-      "nbr":0
+    "ext": {
+      "prebid": {
+        "aliases": {
+          "unknown": "appnexus"
+        }
+      }
+    }
+  },
+  "expectedAmpResponse": {
+    "targeting": {
+      "hb_bidder": "appnexus",
+      "hb_bidder_appnexus": "appnexus",
+      "hb_cache_host": "www.pbcserver.com",
+      "hb_cache_host_appnex": "www.pbcserver.com",
+      "hb_cache_id": "0",
+      "hb_cache_id_appnexus": "0",
+      "hb_cache_path": "/pbcache/endpoint",
+      "hb_cache_path_appnex": "/pbcache/endpoint",
+      "hb_pb": "2.00",
+      "hb_pb_appnexus": "2.00"
     },
+    "warnings": {
+      "general": [
+        {"code": 10002, "message": "debug turned off for account"}
+      ]
+    }
+  },
   "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliases.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliases.json
index be7c2269..7faaa03a 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliases.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/aliases.json
@@ -1,16 +1,19 @@
 {
-  "description": "Well formed amp request with aliases that should run properly",
+  "description": "Amp request impression requests a bid from a bidder alias defined in root.ext.prebid.aliases",
+  "config": {
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 2}
+    ]
+  },
   "mockBidRequest": {
     "id": "some-request-id",
-    "site": {
-      "page": "test.somepage.com"
-    },
+    "site": {"page": "test.somepage.com"},
     "imp": [
       {
         "id": "my-imp-id",
-        "video": {
-          "mimes": [
-            "video/mp4"
+        "banner": {
+          "format": [
+            {"w": 300, "h": 600}
           ]
         },
         "ext": {
@@ -28,10 +31,24 @@
       }
     }
   },
-  "expectedBidResponse": {
-      "id":"some-request-id",
-      "bidid":"test bid id",
-      "nbr":0
+  "expectedAmpResponse": {
+    "targeting": {
+      "hb_bidder": "unknown",
+      "hb_bidder_unknown": "unknown",
+      "hb_cache_host": "www.pbcserver.com",
+      "hb_cache_host_unknow": "www.pbcserver.com",
+      "hb_cache_id": "0",
+      "hb_cache_id_unknown": "0",
+      "hb_cache_path": "/pbcache/endpoint",
+      "hb_cache_path_unknow": "/pbcache/endpoint",
+      "hb_pb": "2.00",
+      "hb_pb_unknown": "2.00"
+    },
+    "warnings": {
+      "general": [
+        {"code": 10002, "message": "debug turned off for account"}
+      ]
+    }
   },
   "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-no-consentstring.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-no-consentstring.json
index 6922dfb2..fe7fb53f 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-no-consentstring.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-no-consentstring.json
@@ -1,52 +1,50 @@
 {
-  "description": "Well formed amp request with GDPR value but missing consent string",
+  "description": "Amp request comes with a root.regs.ext.gdpr value but is missing GDPR consent string",
+  "config": {
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 3}
+    ]
+  },
   "mockBidRequest": {
     "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
-    "site": {
-      "page": "prebid.org",
-      "publisher": {
-        "id": "a3de7af2-a86a-4043-a77b-c7e86744155e"
-      }
-    },
-    "source": {
-      "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5"
-    },
-    "tmax": 1000,
+    "site": {"page": "prebid.org"},
     "imp": [
       {
         "id": "/19968336/header-bid-tag-0",
+        "banner": {
+          "format": [
+            {"w": 300, "h": 600}
+          ]
+        },
         "ext": {
           "appnexus": {
             "placementId": 12883451
           }
-        },
-        "banner": {
-          "format": [
-            {
-              "w": 300,
-              "h": 250
-            },
-            {
-              "w": 300,
-              "h": 300
-            }
-          ]
         }
       }
     ],
     "regs": {
-      "ext": {
-        "gdpr": 1
-      }
-    },
-    "user": {
-      "ext": {}
+      "ext": {"gdpr": 1}
     }
   },
-  "expectedBidResponse": {
-      "id":"b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
-      "bidid":"test bid id",
-      "nbr":0
+  "expectedAmpResponse": {
+    "targeting": {
+      "hb_bidder": "appnexus",
+      "hb_bidder_appnexus": "appnexus",
+      "hb_cache_host": "www.pbcserver.com",
+      "hb_cache_host_appnex": "www.pbcserver.com",
+      "hb_cache_id": "0",
+      "hb_cache_id_appnexus": "0",
+      "hb_cache_path": "/pbcache/endpoint",
+      "hb_cache_path_appnex": "/pbcache/endpoint",
+      "hb_pb": "3.00",
+      "hb_pb_appnexus": "3.00"
+    },
+    "warnings": {
+      "general": [
+        {"code": 10002, "message": "debug turned off for account"}
+      ]
+    }
   },
   "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr.json
index 1e3a2d41..1792d0e6 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr.json
@@ -1,54 +1,56 @@
 {
-  "description": "Well formed amp request with GDPR value and consent string",
+  "description": "Amp request comes with a root.regs.ext.gdpr and a GDPR consent string found in root.user.ext.consent",
+  "config": {
+    "mockBidders": [
+      {"bidderName": "appnexus", "currency": "USD", "price": 15}
+    ]
+  },
   "mockBidRequest": {
     "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
-    "site": {
-      "page": "prebid.org",
-      "publisher": {
-        "id": "a3de7af2-a86a-4043-a77b-c7e86744155e"
-      }
-    },
-    "source": {
-      "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5"
-    },
-    "tmax": 1000,
+    "site": {"page": "prebid.org"},
     "imp": [
       {
         "id": "/19968336/header-bid-tag-0",
-        "ext": {
-          "appnexus": {
-            "placementId": 12883451
-          }
-        },
         "banner": {
           "format": [
             {
               "w": 300,
-              "h": 250
-            },
-            {
-              "w": 300,
-              "h": 300
+              "h": 600
             }
           ]
+        },
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
         }
       }
     ],
     "regs": {
-      "ext": {
-        "gdpr": 1
-      }
+      "ext": {"gdpr": 1}
     },
     "user": {
-      "ext": {
-        "consent": "some-consent-string"
-      }
+      "ext": {"consent": "some-consent-string"}
     }
   },
-  "expectedBidResponse": {
-      "id":"b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
-      "bidid":"test bid id",
-      "nbr":0
+  "expectedAmpResponse": {
+    "targeting": {
+      "hb_bidder": "appnexus",
+      "hb_bidder_appnexus": "appnexus",
+      "hb_cache_host": "www.pbcserver.com",
+      "hb_cache_host_appnex": "www.pbcserver.com",
+      "hb_cache_id": "0",
+      "hb_cache_id_appnexus": "0",
+      "hb_cache_path": "/pbcache/endpoint",
+      "hb_cache_path_appnex": "/pbcache/endpoint",
+      "hb_pb": "15.00",
+      "hb_pb_appnexus": "15.00"
+    },
+    "warnings": {
+      "general": [
+        {"code": 10002, "message": "debug turned off for account"}
+      ]
+    }
   },
   "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-resp.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-resp.json
index 315ec934..a8b8eb54 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-resp.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/imp-with-stored-resp.json
@@ -1,40 +1,50 @@
 {
-  "description": "Well formed amp request with impression with stored response",
+  "description": "Amp request where impression makes reference to a valid stored response with a $5.00 bid",
+  "mockAmpStoredResponse": {
+    "6d718149": "[{\"bid\": [{\"id\": \"bid_id\", \"price\": 5.00, \"ext\": {\"prebid\": {\"targeting\": { \"hb_pb\": \"1.20\", \"hb_cat\": \"IAB-20\", \"hb_cache_id\": \"some_id\"}}}}],\"seat\": \"appnexus\"}]"
+  },
   "mockBidRequest": {
     "id": "request-with-stored-resp",
-    "site": {
-      "page": "test.somepage.com"
-    },
+    "site": {"page": "test.somepage.com"},
     "imp": [
       {
         "id": "my-imp-id",
         "banner": {
           "format": [
-            {
-              "w": 300,
-              "h": 600
-            }
+            {"w": 300, "h": 600}
           ]
         },
         "ext": {
-          "appnexus": {
-            "placementId": 12883451
-          },
           "prebid": {
             "storedauctionresponse": {
               "id": "6d718149"
             }
+          },
+          "appnexus": {
+            "placementId": 12883451
           }
         }
       }
-    ],
-    "user": {
-      "yob": 1989
-    }
+    ]
   },
-  "expectedBidResponse": {
-      "id":"request-with-stored-resp",
-      "bidid":"bid_id"
+  "expectedAmpResponse": {
+    "targeting": {
+      "hb_bidder": "appnexus",
+      "hb_bidder_appnexus": "appnexus",
+      "hb_cache_host": "www.pbcserver.com",
+      "hb_cache_host_appnex": "www.pbcserver.com",
+      "hb_cache_id": "0",
+      "hb_cache_id_appnexus": "0",
+      "hb_cache_path": "/pbcache/endpoint",
+      "hb_cache_path_appnex": "/pbcache/endpoint",
+      "hb_pb": "5.00",
+      "hb_pb_appnexus": "5.00"
+    },
+    "warnings": {
+      "general": [
+        {"code": 10002, "message": "debug turned off for account"}
+      ]
+    }
   },
   "expectedReturnCode": 200
 }
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/user.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/user.json
deleted file mode 100644
index 686489cf..00000000
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/user.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
-  "description": "Well formed amp request with valid User field",
-  "mockBidRequest": {
-    "id": "request-without-user-ext-obj",
-    "site": {
-      "page": "test.somepage.com"
-    },
-    "imp": [
-      {
-        "id": "my-imp-id",
-        "banner": {
-          "format": [
-            {
-              "w": 300,
-              "h": 600
-            }
-          ]
-        },
-        "pmp": {
-          "deals": [
-            {
-              "id": "some-deal-id"
-            }
-          ]
-        },
-        "ext": {
-          "appnexus": {
-            "placementId": 12883451
-          }
-        }
-      }
-    ],
-    "user": {
-      "yob": 1989
-    }
-  },
-  "expectedBidResponse": {
-      "id":"request-without-user-ext-obj",
-      "bidid":"test bid id",
-      "nbr":0
-  },
-  "expectedReturnCode": 200
-}
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index 8cc4f4fe..c3d5f0ec 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -10,16 +10,16 @@ import (
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
-func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]adaptedBidder, []error) {
+func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]AdaptedBidder, []error) {
 	bidders, errs := buildBidders(cfg.Adapters, infos, newAdapterBuilders())
 	if len(errs) > 0 {
 		return nil, errs
 	}
 
-	exchangeBidders := make(map[openrtb_ext.BidderName]adaptedBidder, len(bidders))
+	exchangeBidders := make(map[openrtb_ext.BidderName]AdaptedBidder, len(bidders))
 	for bidderName, bidder := range bidders {
 		info := infos[string(bidderName)]
-		exchangeBidder := adaptBidder(bidder, client, cfg, me, bidderName, info.Debug)
+		exchangeBidder := AdaptBidder(bidder, client, cfg, me, bidderName, info.Debug)
 		exchangeBidder = addValidatedBidderMiddleware(exchangeBidder)
 		exchangeBidders[bidderName] = exchangeBidder
 	}
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 969e4857..74a23998 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -29,7 +29,7 @@ import (
 	"golang.org/x/net/context/ctxhttp"
 )
 
-// adaptedBidder defines the contract needed to participate in an Auction within an Exchange.
+// AdaptedBidder defines the contract needed to participate in an Auction within an Exchange.
 //
 // This interface exists to help segregate core auction logic.
 //
@@ -38,10 +38,10 @@ import (
 //
 // This interface differs from adapters.Bidder to help minimize code duplication across the
 // adapters.Bidder implementations.
-type adaptedBidder interface {
+type AdaptedBidder interface {
 	// requestBid fetches bids for the given request.
 	//
-	// An adaptedBidder *may* return two non-nil values here. Errors should describe situations which
+	// An AdaptedBidder *may* return two non-nil values here. Errors should describe situations which
 	// make the bid (or no-bid) "less than ideal." Common examples include:
 	//
 	// 1. Connection issues.
@@ -56,7 +56,7 @@ type adaptedBidder interface {
 
 const ImpIdReqBody = "Stored bid response for impression id: "
 
-// pbsOrtbBid is a Bid returned by an adaptedBidder.
+// pbsOrtbBid is a Bid returned by an AdaptedBidder.
 //
 // pbsOrtbBid.bid.Ext will become "response.seatbid[i].bid.ext.bidder" in the final OpenRTB response.
 // pbsOrtbBid.bidMeta will become "response.seatbid[i].bid.ext.prebid.meta" in the final OpenRTB response.
@@ -81,11 +81,11 @@ type pbsOrtbBid struct {
 	originalBidCur    string
 }
 
-// pbsOrtbSeatBid is a SeatBid returned by an adaptedBidder.
+// pbsOrtbSeatBid is a SeatBid returned by an AdaptedBidder.
 //
 // This is distinct from the openrtb2.SeatBid so that the prebid-server ext can be passed back with typesafety.
 type pbsOrtbSeatBid struct {
-	// bids is the list of bids which this adaptedBidder wishes to make.
+	// bids is the list of bids which this AdaptedBidder wishes to make.
 	bids []*pbsOrtbBid
 	// currency is the currency in which the bids are made.
 	// Should be a valid currency ISO code.
@@ -95,11 +95,11 @@ type pbsOrtbSeatBid struct {
 	httpCalls []*openrtb_ext.ExtHttpCall
 }
 
-// adaptBidder converts an adapters.Bidder into an exchange.adaptedBidder.
+// AdaptBidder converts an adapters.Bidder into an exchange.AdaptedBidder.
 //
 // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter"
 // (which is being phased out and replaced by Bidder for OpenRTB auctions)
-func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo) adaptedBidder {
+func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo) AdaptedBidder {
 	return &bidderAdapter{
 		Bidder:     bidder,
 		BidderName: name,
diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go
index a1383271..ab1d3904 100644
--- a/exchange/bidder_validate_bids.go
+++ b/exchange/bidder_validate_bids.go
@@ -17,14 +17,14 @@ import (
 //
 // The goal here is to make sure that the response contains Bids which are valid given the initial Request,
 // so that Publishers can trust the Bids they get from Prebid Server.
-func addValidatedBidderMiddleware(bidder adaptedBidder) adaptedBidder {
+func addValidatedBidderMiddleware(bidder AdaptedBidder) AdaptedBidder {
 	return &validatedBidder{
 		bidder: bidder,
 	}
 }
 
 type validatedBidder struct {
-	bidder adaptedBidder
+	bidder AdaptedBidder
 }
 
 func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) {
diff --git a/exchange/exchange.go b/exchange/exchange.go
index fd622efa..d2362107 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -51,7 +51,7 @@ type IdFetcher interface {
 }
 
 type exchange struct {
-	adapterMap        map[openrtb_ext.BidderName]adaptedBidder
+	adapterMap        map[openrtb_ext.BidderName]AdaptedBidder
 	bidderInfo        config.BidderInfos
 	bidderToSyncerKey map[string]string
 	me                metrics.MetricsEngine
@@ -111,7 +111,7 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool {
 	return rand.Intn(100) < 50
 }
 
-func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, vendorListFetcher gdpr.VendorListFetcher, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange {
+func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, vendorListFetcher gdpr.VendorListFetcher, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange {
 	bidderToSyncerKey := map[string]string{}
 	for bidder, syncer := range syncersByBidder {
 		bidderToSyncerKey[bidder] = syncer.Key()
diff --git a/static/bidder-info/medianet.yaml b/static/bidder-info/medianet.yaml
index b9363510..b4b5a211 100644
--- a/static/bidder-info/medianet.yaml
+++ b/static/bidder-info/medianet.yaml
@@ -16,4 +16,4 @@ capabilities:
 userSync:
   redirect:
     url: https://hbx.media.net/cksync.php?cs=1&type=pbs&ovsid=setstatuscode&bidder=medianet&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}
-    userMacro: "${UUID}"
\ No newline at end of file
+    userMacro: "<vsid>"
\ No newline at end of file
diff --git a/static/bidder-info/sovrn.yaml b/static/bidder-info/sovrn.yaml
index 4741c8a1..f26d2d93 100644
--- a/static/bidder-info/sovrn.yaml
+++ b/static/bidder-info/sovrn.yaml
@@ -1,13 +1,16 @@
 maintainer:
   email: "sovrnoss@sovrn.com"
 gvlVendorID: 13
+modifyingVastXmlAllowed: true
 capabilities:
   app:
     mediaTypes:
       - banner
+      - video
   site:
     mediaTypes:
       - banner
+      - video
 userSync:
   redirect:
     url: "https://ap.lijit.com/pixel?redir={{.RedirectURL}}"
diff --git a/static/bidder-params/sovrn.json b/static/bidder-params/sovrn.json
index 5c29b367..6b345b55 100644
--- a/static/bidder-params/sovrn.json
+++ b/static/bidder-params/sovrn.json
@@ -2,8 +2,8 @@
   "$schema": "http://json-schema.org/draft-04/schema#",
   "title": "Sovrn Adapter Params",
   "description": "A schema which validates params accepted by the Sovrn adapter",
-
   "type": "object",
+
   "properties": {
     "tagid": {
       "type": "string",


Test patch:
diff --git a/adapters/sovrn/sovrntest/exemplary/multi-banner.json b/adapters/sovrn/sovrntest/exemplary/multi-banner.json
new file mode 100644
index 00000000..49594103
--- /dev/null
+++ b/adapters/sovrn/sovrntest/exemplary/multi-banner.json
@@ -0,0 +1,177 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      },
+      {
+        "id": "test-imp-id2",
+        "banner": {
+          "format": [
+            {
+              "w": 728,
+              "h": 90
+            },
+            {
+              "w": 160,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "654321"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": ["application/json"],
+          "User-Agent": ["test-user-agent"],
+          "X-Forwarded-For": ["123.123.123.123"],
+          "Accept-Language": ["en"],
+          "Dnt": ["0"],
+          "Cookie": ["ljt_reader=test_reader_id"]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid": "123456",
+              "ext": {
+                "bidder": {
+                  "tagid": "123456"
+                }
+              }
+            },
+            {
+              "id": "test-imp-id2",
+              "banner": {
+                "format": [
+                  {
+                    "w": 728,
+                    "h": 90
+                  },
+                  {
+                    "w": 160,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid": "654321",
+              "ext": {
+                "bidder": {
+                  "tagid": "654321"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "dnt": 0,
+            "language": "en"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id1",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "some-test-ad",
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "impid": "test-imp-id1",
+            "price": 3.5,
+            "adm": "some-test-ad",
+            "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/supplemental/invalid-imp-id.json b/adapters/sovrn/sovrntest/supplemental/invalid-imp-id.json
new file mode 100644
index 00000000..874844dc
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/invalid-imp-id.json
@@ -0,0 +1,123 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": ["application/json"],
+          "User-Agent": ["test-user-agent"],
+          "X-Forwarded-For": ["123.123.123.123"],
+          "Accept-Language": ["en"],
+          "Dnt": ["0"],
+          "Cookie": ["ljt_reader=test_reader_id"]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid": "123456",
+              "ext": {
+                "bidder": {
+                  "tagid": "123456"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "dnt": 0,
+            "language": "en"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-idx",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "some-test-ad",
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Imp ID test-imp-idx in bid didn't match with any imp in the original request",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/supplemental/missing-tagid.json b/adapters/sovrn/sovrntest/supplemental/missing-tagid.json
new file mode 100644
index 00000000..3e79d11f
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/missing-tagid.json
@@ -0,0 +1,46 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Missing required parameter 'tagid'",
+      "comparison": "literal"
+    }
+  ],
+  "httpCalls": []
+}
diff --git a/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json
index 3cd6539f..20c00a48 100644
--- a/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json
+++ b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json
@@ -19,7 +19,7 @@
         "ext": {
           "bidder": {
             "tagid": "123456",
-            "bidfloor": 4.20
+            "bidfloor": 4.2
           }
         }
       }
@@ -60,11 +60,11 @@
                 ]
               },
               "tagid": "123456",
-              "bidfloor": 4.20,
+              "bidfloor": 4.2,
               "ext": {
                 "bidder": {
                   "tagid": "123456",
-                  "bidfloor": 4.20
+                  "bidfloor": 4.2
                 }
               }
             }
diff --git a/adapters/sovrn/sovrntest/supplemental/with-both-custom-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/with-both-custom-default-bidfloor.json
new file mode 100644
index 00000000..4b997b68
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/with-both-custom-default-bidfloor.json
@@ -0,0 +1,126 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "bidfloor": 1.69,
+        "ext": {
+          "bidder": {
+            "tagid": "123456",
+            "bidfloor": 4.20
+          }
+        }
+      }
+    ],
+    "device": { },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": ["application/json"],
+          "Cookie": ["ljt_reader=test_reader_id"]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid": "123456",
+              "bidfloor": 1.69,
+              "ext": {
+                "bidder": {
+                  "tagid": "123456",
+                  "bidfloor": 4.20
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": { }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "some-test-ad",
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "impid": "test-imp-id",
+            "price": 3.5,
+            "adm": "some-test-ad",
+            "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/supplemental/with-only-custom-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/with-only-custom-bidfloor.json
new file mode 100644
index 00000000..b4ab404e
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/with-only-custom-bidfloor.json
@@ -0,0 +1,125 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456",
+            "bidfloor": 4.2
+          }
+        }
+      }
+    ],
+    "device": { },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": ["application/json"],
+          "Cookie": ["ljt_reader=test_reader_id"]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "bidfloor": 4.2,
+              "tagid": "123456",
+              "ext": {
+                "bidder": {
+                  "tagid": "123456",
+                  "bidfloor": 4.20
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": { }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "some-test-ad",
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "impid": "test-imp-id",
+            "price": 3.5,
+            "adm": "some-test-ad",
+            "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/supplemental/with-only-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/with-only-default-bidfloor.json
new file mode 100644
index 00000000..cb74e564
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/with-only-default-bidfloor.json
@@ -0,0 +1,124 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "bidfloor": 1.69,
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      }
+    ],
+    "device": { },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": ["application/json"],
+          "Cookie": ["ljt_reader=test_reader_id"]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid": "123456",
+              "bidfloor": 1.69,
+              "ext": {
+                "bidder": {
+                  "tagid": "123456"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": { }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "some-test-ad",
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "impid": "test-imp-id",
+            "price": 3.5,
+            "adm": "some-test-ad",
+            "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/video/full-video.json b/adapters/sovrn/sovrntest/video/full-video.json
new file mode 100644
index 00000000..18f277ea
--- /dev/null
+++ b/adapters/sovrn/sovrntest/video/full-video.json
@@ -0,0 +1,190 @@
+{
+  "mockBidRequest": {
+  "id": "test-request-id",
+  "imp": [
+    {
+      "id": "test-imp-id",
+      "video": {
+        "mimes": [
+          "video/mp4",
+          "video/3gpp",
+          "video/x-ms-wmv"
+        ],
+        "minduration": 5,
+        "maxduration": 30,
+        "protocols": [
+          4,
+          5,
+          6,
+          8
+        ],
+        "w": 300,
+        "h": 250,
+        "startdelay": 1,
+        "placement": 1,
+        "linearity": 1,
+        "skip": 1,
+        "skipmin": 10,
+        "skipafter": 5,
+        "sequence": 1,
+        "battr": [ 10, 17 ],
+        "maxextended": 0,
+        "minbitrate": 10,
+        "maxbitrate": 100,
+        "boxingallowed": 1,
+        "playbackmethod": [ 2, 6 ],
+        "playbackend": 1,
+        "delivery": [ 1, 2 ],
+        "pos": 1,
+        "api": [ 1, 2 ]
+      },
+      "ext": {
+        "bidder": {
+          "tagid": "123456"
+        }
+      }
+    }
+  ],
+  "device": {
+    "ua": "test-user-agent",
+    "ip": "123.123.123.123",
+    "language": "en",
+    "dnt": 0
+  },
+  "site": {
+    "domain": "www.publisher.com",
+    "page": "http://www.publisher.com/awesome/site"
+  },
+  "user": {
+    "buyeruid": "test_reader_id"
+  }
+},
+  "httpCalls": [
+    {
+      "expectedRequest":
+      {
+        "Accept": [
+          "application/json"
+        ],
+        "headers": {
+          "Content-Type": [
+            "application/json"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Accept-Language": [
+            "en"
+          ],
+          "Dnt": [
+            "0"
+          ],
+          "Cookie": [
+            "ljt_reader=test_reader_id"
+          ]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/3gpp",
+                  "video/x-ms-wmv"
+                ],
+                "minduration": 5,
+                "maxduration": 30,
+                "protocols": [
+                  4,
+                  5,
+                  6,
+                  8
+                ],
+                "w": 300,
+                "h": 250,
+                "startdelay": 1,
+                "placement": 1,
+                "linearity": 1,
+                "skip": 1,
+                "skipmin": 10,
+                "skipafter": 5,
+                "sequence": 1,
+                "battr": [ 10, 17 ],
+                "minbitrate": 10,
+                "maxbitrate": 100,
+                "boxingallowed": 1,
+                "playbackmethod": [ 2, 6 ],
+                "playbackend": 1,
+                "delivery": [ 1, 2 ],
+                "pos": 1,
+                "api": [ 1, 2 ]
+              },
+              "tagid": "123456", 
+              "ext": {
+                "bidder": {
+                  "tagid": "123456"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "dnt": 0,
+            "language": "en"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "v_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"12345\"><InLine><AdSystem version=\"1.0\">SpotXchange</AdSystem><AdTitle> <![CDATA[Sample VAST]]></AdTitle><Impression>http://sample.com</Impression><Description><![CDATA[A sample VAST feed]]></Description> <Creatives><Creative sequence=\"1\" id=\"1\"><Linear><Duration>00:00:30</Duration> <TrackingEvents> </TrackingEvents><VideoClicks><ClickThrough><![CDATA[http://sample.com/openrtbtest]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery=\"progressive\" bitrate=\"256\" width=\"640\" height=\"480\" type=\"video/mp4\"><![CDATA[http://sample.com/video.mp4]]> </MediaFile></MediaFiles></Linear> </Creative></Creatives></InLine></Ad></VAST>"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+          "id": "v_449642_554a13d3b9f348fba707cf83f0f63800",
+          "impid": "test-imp-id",
+          "price": 3.5,
+          "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+          "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"12345\"><InLine><AdSystem version=\"1.0\">SpotXchange</AdSystem><AdTitle> <![CDATA[Sample VAST]]></AdTitle><Impression>http://sample.com</Impression><Description><![CDATA[A sample VAST feed]]></Description> <Creatives><Creative sequence=\"1\" id=\"1\"><Linear><Duration>00:00:30</Duration> <TrackingEvents> </TrackingEvents><VideoClicks><ClickThrough><![CDATA[http://sample.com/openrtbtest]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery=\"progressive\" bitrate=\"256\" width=\"640\" height=\"480\" type=\"video/mp4\"><![CDATA[http://sample.com/video.mp4]]> </MediaFile></MediaFiles></Linear> </Creative></Creatives></InLine></Ad></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/video/multi-banner-video.json b/adapters/sovrn/sovrntest/video/multi-banner-video.json
new file mode 100644
index 00000000..abfa1ab3
--- /dev/null
+++ b/adapters/sovrn/sovrntest/video/multi-banner-video.json
@@ -0,0 +1,205 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id1",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      },
+    {
+      "id": "test-imp-id2",
+      "video": {
+        "mimes": [
+          "video/mp4",
+          "video/3gpp",
+          "video/x-ms-wmv"
+        ],
+        "minduration": 10,
+        "maxduration": 60,
+        "protocols": [
+          4,
+          5,
+          6,
+          8
+        ]
+      },
+      "ext": {
+        "bidder": {
+          "tagid": "654321"
+        }
+      },
+      "tagid": "654321" 
+    }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": ["application/json"],
+          "User-Agent": ["test-user-agent"],
+          "X-Forwarded-For": ["123.123.123.123"],
+          "Accept-Language": ["en"],
+          "Dnt": ["0"],
+          "Cookie": ["ljt_reader=test_reader_id"]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id1",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid": "123456",
+              "ext": {
+                "bidder": {
+                  "tagid": "123456"
+                }
+              }
+            },
+            {
+              "id": "test-imp-id2",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/3gpp",
+                  "video/x-ms-wmv"
+                ],
+                "minduration": 10,
+                "maxduration": 60,
+                "protocols": [
+                  4,
+                  5,
+                  6,
+                  8
+                ]
+              },
+              "tagid": "654321",
+              "ext": {
+                "bidder": {
+                  "tagid": "654321"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "dnt": 0,
+            "language": "en"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id1",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "some-test-ad",
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            },
+            {
+              "bid": [
+                {
+                  "id": "v_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id2",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"12345\"><InLine><AdSystem version=\"1.0\">SpotXchange</AdSystem><AdTitle> <![CDATA[Sample VAST]]></AdTitle><Impression>http://sample.com</Impression><Description><![CDATA[A sample VAST feed]]></Description> <Creatives><Creative sequence=\"1\" id=\"1\"><Linear><Duration>00:00:30</Duration> <TrackingEvents> </TrackingEvents><VideoClicks><ClickThrough><![CDATA[http://sample.com/openrtbtest]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery=\"progressive\" bitrate=\"256\" width=\"640\" height=\"480\" type=\"video/mp4\"><![CDATA[http://sample.com/video.mp4]]> </MediaFile></MediaFiles></Linear> </Creative></Creatives></InLine></Ad></VAST>"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "impid": "test-imp-id1",
+            "price": 3.5,
+            "adm": "some-test-ad",
+            "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        },
+        {
+          "bid": {
+            "id": "v_449642_554a13d3b9f348fba707cf83f0f63800",
+            "impid": "test-imp-id2",
+            "price": 3.5,
+            "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"12345\"><InLine><AdSystem version=\"1.0\">SpotXchange</AdSystem><AdTitle> <![CDATA[Sample VAST]]></AdTitle><Impression>http://sample.com</Impression><Description><![CDATA[A sample VAST feed]]></Description> <Creatives><Creative sequence=\"1\" id=\"1\"><Linear><Duration>00:00:30</Duration> <TrackingEvents> </TrackingEvents><VideoClicks><ClickThrough><![CDATA[http://sample.com/openrtbtest]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery=\"progressive\" bitrate=\"256\" width=\"640\" height=\"480\" type=\"video/mp4\"><![CDATA[http://sample.com/video.mp4]]> </MediaFile></MediaFiles></Linear> </Creative></Creatives></InLine></Ad></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/video/multi-video.json b/adapters/sovrn/sovrntest/video/multi-video.json
new file mode 100644
index 00000000..5fdcadcc
--- /dev/null
+++ b/adapters/sovrn/sovrntest/video/multi-video.json
@@ -0,0 +1,198 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id1",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/3gpp",
+            "video/x-ms-wmv"
+          ],
+          "minduration": 5,
+          "maxduration": 30,
+          "protocols": [
+            4,
+            5,
+            6,
+            8
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      },
+      {
+        "id": "test-imp-id2",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/3gpp",
+            "video/x-ms-wmv"
+          ],
+          "minduration": 10,
+          "maxduration": 60,
+          "protocols": [
+            4,
+            5,
+            6,
+            8
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "654321"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "Accept": [
+          "application/json"
+        ],
+        "headers": {
+          "Content-Type": [
+            "application/json"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Accept-Language": [
+            "en"
+          ],
+          "Dnt": [
+            "0"
+          ],
+          "Cookie": [
+            "ljt_reader=test_reader_id"
+          ]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id1",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/3gpp",
+                  "video/x-ms-wmv"
+                ],
+                "minduration": 5,
+                "maxduration": 30,
+                "protocols": [
+                  4,
+                  5,
+                  6,
+                  8
+                ]
+              },
+              "tagid": "123456",
+              "ext": {
+                "bidder": {
+                  "tagid": "123456"
+                }
+              }
+            },
+            {
+              "id": "test-imp-id2",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/3gpp",
+                  "video/x-ms-wmv"
+                ],
+                "minduration": 10,
+                "maxduration": 60,
+                "protocols": [
+                  4,
+                  5,
+                  6,
+                  8
+                ]
+              },
+              "tagid": "654321",
+              "ext": {
+                "bidder": {
+                  "tagid": "654321"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "dnt": 0,
+            "language": "en"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "v_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id2",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"12345\"><InLine><AdSystem version=\"1.0\">SpotXchange</AdSystem><AdTitle> <![CDATA[Sample VAST]]></AdTitle><Impression>http://sample.com</Impression><Description><![CDATA[A sample VAST feed]]></Description> <Creatives><Creative sequence=\"1\" id=\"1\"><Linear><Duration>00:00:30</Duration> <TrackingEvents> </TrackingEvents><VideoClicks><ClickThrough><![CDATA[http://sample.com/openrtbtest]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery=\"progressive\" bitrate=\"256\" width=\"640\" height=\"480\" type=\"video/mp4\"><![CDATA[http://sample.com/video.mp4]]> </MediaFile></MediaFiles></Linear> </Creative></Creatives></InLine></Ad></VAST>"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "v_449642_554a13d3b9f348fba707cf83f0f63800",
+            "impid": "test-imp-id2",
+            "price": 3.5,
+            "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"12345\"><InLine><AdSystem version=\"1.0\">SpotXchange</AdSystem><AdTitle> <![CDATA[Sample VAST]]></AdTitle><Impression>http://sample.com</Impression><Description><![CDATA[A sample VAST feed]]></Description> <Creatives><Creative sequence=\"1\" id=\"1\"><Linear><Duration>00:00:30</Duration> <TrackingEvents> </TrackingEvents><VideoClicks><ClickThrough><![CDATA[http://sample.com/openrtbtest]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery=\"progressive\" bitrate=\"256\" width=\"640\" height=\"480\" type=\"video/mp4\"><![CDATA[http://sample.com/video.mp4]]> </MediaFile></MediaFiles></Linear> </Creative></Creatives></InLine></Ad></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/video/no-bid.json b/adapters/sovrn/sovrntest/video/no-bid.json
new file mode 100644
index 00000000..7c9bc4c7
--- /dev/null
+++ b/adapters/sovrn/sovrntest/video/no-bid.json
@@ -0,0 +1,124 @@
+{
+  "mockBidRequest": {
+  "id": "test-request-id",
+  "imp": [
+    {
+      "id": "test-imp-id",
+      "video": {
+        "mimes": [
+          "video/mp4",
+          "video/3gpp",
+          "video/x-ms-wmv"
+        ],
+        "minduration": 5,
+        "maxduration": 30,
+        "protocols": [
+          4,
+          5,
+          6,
+          8
+        ]
+      },
+      "ext": {
+        "bidder": {
+          "tagid": "123456"
+        }
+      }
+    }
+  ],
+  "device": {
+    "ua": "test-user-agent",
+    "ip": "123.123.123.123",
+    "language": "en",
+    "dnt": 0
+  },
+  "site": {
+    "domain": "www.publisher.com",
+    "page": "http://www.publisher.com/awesome/site"
+  },
+  "user": {
+    "buyeruid": "test_reader_id"
+  }
+},
+  "httpCalls": [
+    {
+      "expectedRequest":
+      {
+        "Accept": [
+          "application/json"
+        ],
+        "headers": {
+          "Content-Type": [
+            "application/json"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Accept-Language": [
+            "en"
+          ],
+          "Dnt": [
+            "0"
+          ],
+          "Cookie": [
+            "ljt_reader=test_reader_id"
+          ]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/3gpp",
+                  "video/x-ms-wmv"
+                ],
+                "minduration": 5,
+                "maxduration": 30,
+                "protocols": [
+                  4,
+                  5,
+                  6,
+                  8
+                ]
+              },
+              "tagid": "123456",
+              "ext": {
+                "bidder": {
+                  "tagid": "123456"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "dnt": 0,
+            "language": "en"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id"
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [{}]
+}
diff --git a/adapters/sovrn/sovrntest/video/simple-video.json b/adapters/sovrn/sovrntest/video/simple-video.json
new file mode 100644
index 00000000..9cfeef5e
--- /dev/null
+++ b/adapters/sovrn/sovrntest/video/simple-video.json
@@ -0,0 +1,153 @@
+{
+  "mockBidRequest": {
+  "id": "test-request-id",
+  "imp": [
+    {
+      "id": "test-imp-id",
+      "video": {
+        "mimes": [
+          "video/mp4",
+          "video/3gpp",
+          "video/x-ms-wmv"
+        ],
+        "minduration": 5,
+        "maxduration": 30,
+        "protocols": [
+          4,
+          5,
+          6,
+          8
+        ]
+      },
+      "ext": {
+        "bidder": {
+          "tagid": "123456"
+        }
+      }
+    }
+  ],
+  "device": {
+    "ua": "test-user-agent",
+    "ip": "123.123.123.123",
+    "language": "en",
+    "dnt": 0
+  },
+  "site": {
+    "domain": "www.publisher.com",
+    "page": "http://www.publisher.com/awesome/site"
+  },
+  "user": {
+    "buyeruid": "test_reader_id"
+  }
+},
+  "httpCalls": [
+    {
+      "expectedRequest":
+      {
+        "Accept": [
+          "application/json"
+        ],
+        "headers": {
+          "Content-Type": [
+            "application/json"
+          ],
+          "User-Agent": [
+            "test-user-agent"
+          ],
+          "X-Forwarded-For": [
+            "123.123.123.123"
+          ],
+          "Accept-Language": [
+            "en"
+          ],
+          "Dnt": [
+            "0"
+          ],
+          "Cookie": [
+            "ljt_reader=test_reader_id"
+          ]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "video": {
+                "mimes": [
+                  "video/mp4",
+                  "video/3gpp",
+                  "video/x-ms-wmv"
+                ],
+                "minduration": 5,
+                "maxduration": 30,
+                "protocols": [
+                  4,
+                  5,
+                  6,
+                  8
+                ]
+              },
+              "tagid": "123456",
+              "ext": {
+                "bidder": {
+                  "tagid": "123456"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": {
+            "ua": "test-user-agent",
+            "ip": "123.123.123.123",
+            "dnt": 0,
+            "language": "en"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "v_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"12345\"><InLine><AdSystem version=\"1.0\">SpotXchange</AdSystem><AdTitle> <![CDATA[Sample VAST]]></AdTitle><Impression>http://sample.com</Impression><Description><![CDATA[A sample VAST feed]]></Description> <Creatives><Creative sequence=\"1\" id=\"1\"><Linear><Duration>00:00:30</Duration> <TrackingEvents> </TrackingEvents><VideoClicks><ClickThrough><![CDATA[http://sample.com/openrtbtest]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery=\"progressive\" bitrate=\"256\" width=\"640\" height=\"480\" type=\"video/mp4\"><![CDATA[http://sample.com/video.mp4]]> </MediaFile></MediaFiles></Linear> </Creative></Creatives></InLine></Ad></VAST>"
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+          "id": "v_449642_554a13d3b9f348fba707cf83f0f63800",
+          "impid": "test-imp-id",
+          "price": 3.5,
+          "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+          "adm": "<?xml version=\"1.0\" encoding=\"utf-8\"?><VAST version=\"2.0\"><Ad id=\"12345\"><InLine><AdSystem version=\"1.0\">SpotXchange</AdSystem><AdTitle> <![CDATA[Sample VAST]]></AdTitle><Impression>http://sample.com</Impression><Description><![CDATA[A sample VAST feed]]></Description> <Creatives><Creative sequence=\"1\" id=\"1\"><Linear><Duration>00:00:30</Duration> <TrackingEvents> </TrackingEvents><VideoClicks><ClickThrough><![CDATA[http://sample.com/openrtbtest]]></ClickThrough></VideoClicks><MediaFiles><MediaFile delivery=\"progressive\" bitrate=\"256\" width=\"640\" height=\"480\" type=\"video/mp4\"><![CDATA[http://sample.com/video.mp4]]> </MediaFile></MediaFiles></Linear> </Creative></Creatives></InLine></Ad></VAST>"
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/videosupplemental/missing-all-required-params.json b/adapters/sovrn/sovrntest/videosupplemental/missing-all-required-params.json
new file mode 100644
index 00000000..97fa779c
--- /dev/null
+++ b/adapters/sovrn/sovrntest/videosupplemental/missing-all-required-params.json
@@ -0,0 +1,36 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {},
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Missing required video parameter",
+      "comparison": "literal"
+    }
+  ],
+  "httpCalls": []
+}
diff --git a/adapters/sovrn/sovrntest/videosupplemental/no-maxduration.json b/adapters/sovrn/sovrntest/videosupplemental/no-maxduration.json
new file mode 100644
index 00000000..c333be9a
--- /dev/null
+++ b/adapters/sovrn/sovrntest/videosupplemental/no-maxduration.json
@@ -0,0 +1,49 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/3gpp",
+            "video/x-ms-wmv"
+          ],
+          "minduration": 5,
+          "protocols": [
+            4,
+            5,
+            6,
+            8
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Missing required video parameter",
+      "comparison": "literal"
+    }
+  ],
+  "httpCalls": []
+}
diff --git a/adapters/sovrn/sovrntest/videosupplemental/no-mimes.json b/adapters/sovrn/sovrntest/videosupplemental/no-mimes.json
new file mode 100644
index 00000000..f2e3124c
--- /dev/null
+++ b/adapters/sovrn/sovrntest/videosupplemental/no-mimes.json
@@ -0,0 +1,45 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "minduration": 5,
+          "maxduration": 30,
+          "protocols": [
+            4,
+            5,
+            6,
+            8
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Missing required video parameter",
+      "comparison": "literal"
+    }
+  ],
+  "httpCalls": []
+}
diff --git a/adapters/sovrn/sovrntest/videosupplemental/no-minduration.json b/adapters/sovrn/sovrntest/videosupplemental/no-minduration.json
new file mode 100644
index 00000000..88703dda
--- /dev/null
+++ b/adapters/sovrn/sovrntest/videosupplemental/no-minduration.json
@@ -0,0 +1,49 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/3gpp",
+            "video/x-ms-wmv"
+          ],
+          "maxduration": 30,
+          "protocols": [
+            4,
+            5,
+            6,
+            8
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Missing required video parameter",
+      "comparison": "literal"
+    }
+  ],
+  "httpCalls": []
+}
diff --git a/adapters/sovrn/sovrntest/videosupplemental/no-protocols.json b/adapters/sovrn/sovrntest/videosupplemental/no-protocols.json
new file mode 100644
index 00000000..9fb56375
--- /dev/null
+++ b/adapters/sovrn/sovrntest/videosupplemental/no-protocols.json
@@ -0,0 +1,44 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "mimes": [
+            "video/mp4",
+            "video/3gpp",
+            "video/x-ms-wmv"
+          ],
+          "minduration": 5,
+          "maxduration": 30
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      }
+    ],
+    "device": {
+      "ua": "test-user-agent",
+      "ip": "123.123.123.123",
+      "language": "en",
+      "dnt": 0
+    },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Missing required video parameter",
+      "comparison": "literal"
+    }
+  ],
+  "httpCalls": []
+}
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 1999f2da..a25efab0 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -24,69 +25,70 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-// From auction_test.go
-// const maxSize = 1024 * 256
-
 // TestGoodRequests makes sure that the auction runs properly-formatted stored bids correctly.
 func TestGoodAmpRequests(t *testing.T) {
-	goodRequests := map[string]json.RawMessage{
-		"1": json.RawMessage(validRequest(t, "aliased-buyeruids.json")),
-		"2": json.RawMessage(validRequest(t, "aliases.json")),
-		"3": json.RawMessage(validRequest(t, "imp-with-stored-resp.json")),
-		"5": json.RawMessage(validRequest(t, "gdpr-no-consentstring.json")),
-		"6": json.RawMessage(validRequest(t, "gdpr.json")),
-		"7": json.RawMessage(validRequest(t, "site.json")),
-		"9": json.RawMessage(validRequest(t, "user.json")),
-	}
+	testCases := []struct {
+		storedReqID string
+		filename    string
+	}{
+		{"1", "aliased-buyeruids.json"},
+		{"2", "aliases.json"},
+		{"3", "imp-with-stored-resp.json"},
+		{"5", "gdpr-no-consentstring.json"},
+		{"6", "gdpr.json"},
+	}
+
+	for _, tc := range testCases {
+		// Read test case and unmarshal
+		fileJsonData, err := ioutil.ReadFile("sample-requests/valid-whole/supplementary/" + tc.filename)
+		if !assert.NoError(t, err, "Failed to fetch a valid request: %v. Test file: %s", err, tc.filename) {
+			continue
+		}
 
-	goodStoredResponses := map[string]json.RawMessage{
-		"6d718149": json.RawMessage(`[{"bid": [{"id": "bid_id", "ext": {"prebid": {"targeting": { "hb_pb": "1.20", "hb_cat": "IAB-20", "hb_cache_id": "some_id"}}}}],"seat": "appnexus"}]`),
-	}
+		test := testCase{}
+		if !assert.NoError(t, json.Unmarshal(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", tc.filename, err) {
+			continue
+		}
+		test.storedRequest = map[string]json.RawMessage{tc.storedReqID: test.BidRequest}
+		test.endpointType = AMP_ENDPOINT
+
+		cfg := &config.Configuration{MaxRequestSize: maxSize}
+		if test.Config != nil {
+			cfg.BlacklistedApps = test.Config.BlacklistedApps
+			cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap()
+			cfg.BlacklistedAccts = test.Config.BlacklistedAccounts
+			cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap()
+			cfg.AccountRequired = test.Config.AccountRequired
+		}
 
-	endpoint, _ := NewAmpEndpoint(
-		fakeUUIDGenerator{},
-		&mockAmpExchange{},
-		newParamsValidator(t),
-		&mockAmpStoredReqFetcher{goodRequests},
-		empty_fetcher.EmptyFetcher{},
-		&config.Configuration{MaxRequestSize: maxSize},
-		&metricsConfig.NilMetricsEngine{},
-		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
-		map[string]string{},
-		[]byte{},
-		openrtb_ext.BuildBidderMap(),
-		&mockAmpStoredResponseFetcher{goodStoredResponses},
-	)
+		// Set test up
+		ampEndpoint, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
+		if !assert.NoError(t, err) {
+			continue
+		}
 
-	for requestID := range goodRequests {
-		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s", requestID), nil)
+		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s", tc.storedReqID), nil)
 		recorder := httptest.NewRecorder()
-		endpoint(recorder, request, nil)
-
-		if recorder.Code != http.StatusOK {
-			t.Errorf("Expected status %d. Got %d. Request config ID was %s", http.StatusOK, recorder.Code, requestID)
-			t.Errorf("Response body was: %s", recorder.Body)
-			t.Errorf("Request was: %s", string(goodRequests[requestID]))
-		}
 
-		var response AmpResponse
-		if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
-			t.Errorf("AMP response was: %s", recorder.Body.Bytes())
-			t.Fatalf("Error unmarshalling response: %s", err.Error())
-		}
+		// runTestCase
+		ampEndpoint(recorder, request, nil)
 
-		if response.Targeting == nil || len(response.Targeting) == 0 {
-			t.Errorf("Bad response, no targeting data.\n Response was: %v", recorder.Body)
+		// Close servers
+		for _, mockBidServer := range mockBidServers {
+			mockBidServer.Close()
 		}
-		if len(response.Targeting) != 3 {
-			t.Errorf("Bad targeting data. Expected 3 keys, got %d.", len(response.Targeting))
-		}
-
-		if response.Debug != nil {
-			t.Errorf("Debug present but not requested")
+		mockCurrencyRatesServer.Close()
+
+		// Assertions
+		if assert.Equal(t, test.ExpectedReturnCode, recorder.Code, "Expected status %d. Got %d. Amp test file: %s", http.StatusOK, recorder.Code, tc.filename) {
+			if test.ExpectedReturnCode == http.StatusOK {
+				var ampResponse AmpResponse
+				assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &ampResponse), "Error unmarshalling ampResponse: %v", err)
+				assert.Equal(t, test.ExpectedAmpResponse, ampResponse, "Test file: %s", tc.filename)
+			} else {
+				assert.Equal(t, test.ExpectedErrorMessage, recorder.Body.String(), tc.filename)
+			}
 		}
-
-		assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors, "errors")
 	}
 }
 
@@ -642,7 +644,10 @@ func TestAMPSiteExt(t *testing.T) {
 
 // TestBadRequests makes sure we return 400's on bad requests.
 func TestAmpBadRequests(t *testing.T) {
-	files := fetchFiles(t, "sample-requests/invalid-whole")
+	dir := "sample-requests/invalid-whole"
+	files, err := ioutil.ReadDir(dir)
+	assert.NoError(t, err, "Failed to read folder: %s", dir)
+
 	badRequests := make(map[string]json.RawMessage, len(files))
 	for index, file := range files {
 		badRequests[strconv.Itoa(100+index)] = readFile(t, "sample-requests/invalid-whole/"+file.Name())
@@ -955,30 +960,6 @@ func (s formatOverrideSpec) execute(t *testing.T) {
 	}
 }
 
-type mockAmpStoredReqFetcher struct {
-	data map[string]json.RawMessage
-}
-
-func (cf *mockAmpStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
-	return cf.data, nil, nil
-}
-
-func (cf *mockAmpStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
-	return nil, nil
-}
-
-type mockAmpStoredResponseFetcher struct {
-	data map[string]json.RawMessage
-}
-
-func (cf *mockAmpStoredResponseFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
-	return nil, nil, nil
-}
-
-func (cf *mockAmpStoredResponseFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
-	return cf.data, nil
-}
-
 type mockAmpExchange struct {
 	lastRequest *openrtb2.BidRequest
 }
diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go
index 5cae8e83..eb50b57e 100644
--- a/endpoints/openrtb2/auction_benchmark_test.go
+++ b/endpoints/openrtb2/auction_benchmark_test.go
@@ -1,7 +1,10 @@
 package openrtb2
 
 import (
+	"bytes"
 	"context"
+	"fmt"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"strings"
@@ -61,7 +64,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
 	server := httptest.NewServer(http.HandlerFunc(benchmarkTestServer))
 	defer server.Close()
 
-	var infos config.BidderInfos
+	var infos = make(config.BidderInfos, 0)
 	infos["appnexus"] = config.BidderInfo{Capabilities: &config.CapabilitiesInfo{Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}}
 	paramValidator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
 	if err != nil {
@@ -107,3 +110,60 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
 		endpoint(httptest.NewRecorder(), benchmarkBuildTestRequest(), nil)
 	}
 }
+
+// BenchmarkValidWholeExemplary benchmarks the process that results of hitting the `openrtb2/auction` with
+// the different JSON bid requests found in the `sample-requests/valid-whole/exemplary/` directory. As
+// especified in said file, we expect this bid request to succeed with a 200 status code.
+func BenchmarkValidWholeExemplary(b *testing.B) {
+	var benchInput = []string{
+		"sample-requests/valid-whole/exemplary/all-ext.json",
+		"sample-requests/valid-whole/exemplary/interstitial-no-size.json",
+		"sample-requests/valid-whole/exemplary/prebid-test-ad.json",
+		"sample-requests/valid-whole/exemplary/simple.json",
+		"sample-requests/valid-whole/exemplary/skadn.json",
+	}
+
+	for _, testFile := range benchInput {
+		b.Run(fmt.Sprintf("input_file_%s", testFile), func(b *testing.B) {
+			b.StopTimer()
+			// Set up
+			fileData, err := ioutil.ReadFile(testFile)
+			if err != nil {
+				b.Fatalf("unable to read file %s", testFile)
+			}
+			test, err := parseTestFile(fileData, testFile)
+			if err != nil {
+				b.Fatal(err.Error())
+			}
+			test.endpointType = OPENRTB_ENDPOINT
+
+			cfg := &config.Configuration{
+				MaxRequestSize:     maxSize,
+				BlacklistedApps:    test.Config.BlacklistedApps,
+				BlacklistedAppMap:  test.Config.getBlacklistedAppMap(),
+				BlacklistedAccts:   test.Config.BlacklistedAccounts,
+				BlacklistedAcctMap: test.Config.getBlackListedAccountMap(),
+				AccountRequired:    test.Config.AccountRequired,
+			}
+
+			auctionEndpointHandler, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
+			if err != nil {
+				b.Fatal(err.Error())
+			}
+			request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(test.BidRequest))
+			recorder := httptest.NewRecorder()
+
+			// Run benchmark
+			b.ResetTimer()
+			for n := 0; n < b.N; n++ {
+				b.StartTimer()
+				auctionEndpointHandler(recorder, request, nil) //Request comes from the unmarshalled mockBidRequest
+				b.StopTimer()
+			}
+			for _, mockBidServer := range mockBidServers {
+				mockBidServer.Close()
+			}
+			mockCurrencyRatesServer.Close()
+		})
+	}
+}
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index e1138587..cd2a065f 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -5,21 +5,19 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
-	"fmt"
 	"io"
 	"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httptest"
-	"os"
 	"path/filepath"
 	"strings"
 	"testing"
 	"time"
 
+	"github.com/julienschmidt/httprouter"
 	analyticsConf "github.com/prebid/prebid-server/analytics/config"
 	"github.com/prebid/prebid-server/config"
-	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/exchange"
 	"github.com/prebid/prebid-server/metrics"
@@ -36,26 +34,6 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-const maxSize = 1024 * 256
-
-type testCase struct {
-	BidRequest           json.RawMessage   `json:"mockBidRequest"`
-	Config               *testConfigValues `json:"config"`
-	ExpectedReturnCode   int               `json:"expectedReturnCode,omitempty"`
-	ExpectedErrorMessage string            `json:"expectedErrorMessage"`
-	ExpectedBidResponse  json.RawMessage   `json:"expectedBidResponse"`
-}
-
-type testConfigValues struct {
-	AccountRequired     bool                          `json:"accountRequired"`
-	AliasJSON           string                        `json:"aliases"`
-	BlacklistedAccounts []string                      `json:"blacklistedAccts"`
-	BlacklistedApps     []string                      `json:"blacklistedApps"`
-	DisabledAdapters    []string                      `json:"disabledAdapters"`
-	CurrencyRates       map[string]map[string]float64 `json:"currencyRates"`
-	MockBidder          mockBidExchangeBidder         `json:"mockBidder"`
-}
-
 func TestJsonSampleRequests(t *testing.T) {
 	testSuites := []struct {
 		description          string
@@ -122,45 +100,57 @@ func TestJsonSampleRequests(t *testing.T) {
 			"currency-conversion/custom-rates/errors",
 		},
 	}
-	for _, test := range testSuites {
-		testCaseFiles, err := getTestFiles(filepath.Join("sample-requests", test.sampleRequestsSubDir))
-		if assert.NoError(t, err, "Test case %s. Error reading files from directory %s \n", test.description, test.sampleRequestsSubDir) {
-			for _, file := range testCaseFiles {
-				data, err := ioutil.ReadFile(file)
-				if assert.NoError(t, err, "Test case %s. Error reading file %s \n", test.description, file) {
-					runTestCase(t, data, file)
-				}
-			}
-		}
-	}
-}
 
-func getTestFiles(dir string) ([]string, error) {
-	var filesToAssert []string
+	for _, tc := range testSuites {
+		testCaseFiles, err := getTestFiles(filepath.Join("sample-requests", tc.sampleRequestsSubDir))
+		if assert.NoError(t, err, "Test case %s. Error reading files from directory %s \n", tc.description, tc.sampleRequestsSubDir) {
+			for _, testFile := range testCaseFiles {
+				fileData, err := ioutil.ReadFile(testFile)
+				if assert.NoError(t, err, "Test case %s. Error reading file %s \n", tc.description, testFile) {
+					// Retrieve test case input and expected output from JSON file
+					test, err := parseTestFile(fileData, testFile)
+					if !assert.NoError(t, err) {
+						continue
+					}
 
-	fileList, err := ioutil.ReadDir(dir)
-	if err != nil {
-		return nil, err
-	}
+					// Build endpoint for testing. If no error, run test case
+					cfg := &config.Configuration{MaxRequestSize: maxSize}
+					if test.Config != nil {
+						cfg.BlacklistedApps = test.Config.BlacklistedApps
+						cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap()
+						cfg.BlacklistedAccts = test.Config.BlacklistedAccounts
+						cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap()
+						cfg.AccountRequired = test.Config.AccountRequired
+					}
+					test.endpointType = OPENRTB_ENDPOINT
 
-	// Append the path of every file found in `dir` to the `filesToAssert` array
-	for _, fileInfo := range fileList {
-		filesToAssert = append(filesToAssert, filepath.Join(dir, fileInfo.Name()))
-	}
+					auctionEndpointHandler, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
+					if assert.NoError(t, err) {
+						runTestCase(t, auctionEndpointHandler, test, fileData, testFile)
+					}
 
-	return filesToAssert, nil
+					// Close servers regardless if the test case was run or not
+					for _, mockBidServer := range mockBidServers {
+						mockBidServer.Close()
+					}
+					mockCurrencyRatesServer.Close()
+				}
+			}
+		}
+	}
 }
 
-func runTestCase(t *testing.T, fileData []byte, testFile string) {
+func runTestCase(t *testing.T, auctionEndpointHandler httprouter.Handle, test testCase, fileData []byte, testFile string) {
 	t.Helper()
 
-	// Retrieve values from JSON file
-	test := parseTestFile(t, fileData, testFile)
-
-	// Run test
-	actualCode, actualJsonBidResponse := doRequest(t, test)
+	// Hit the auction endpoint with the test case configuration and mockBidRequest
+	request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(test.BidRequest))
+	recorder := httptest.NewRecorder()
+	auctionEndpointHandler(recorder, request, nil)
 
 	// Assertions
+	actualCode := recorder.Code
+	actualJsonBidResponse := recorder.Body.String()
 	assert.Equal(t, test.ExpectedReturnCode, actualCode, "Test failed. Filename: %s \n", testFile)
 
 	// Either assert bid response or expected error
@@ -183,78 +173,6 @@ func runTestCase(t *testing.T, fileData []byte, testFile string) {
 	}
 }
 
-func parseTestFile(t *testing.T, fileData []byte, testFile string) testCase {
-	t.Helper()
-
-	parsedTestData := testCase{}
-	var err, errEm error
-
-	// Get testCase values
-	parsedTestData.BidRequest, _, _, err = jsonparser.Get(fileData, "mockBidRequest")
-	assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", testFile, err)
-
-	// Get testCaseConfig values
-	parsedTestData.Config = &testConfigValues{}
-	var jsonTestConfig json.RawMessage
-
-	jsonTestConfig, _, _, err = jsonparser.Get(fileData, "config")
-	if err == nil {
-		err = json.Unmarshal(jsonTestConfig, parsedTestData.Config)
-		assert.NoError(t, err, "Error unmarshaling root.config from file %s. Desc: %v.", testFile, err)
-	}
-
-	// Get the return code we expect PBS to throw back given test's bidRequest and config
-	parsedReturnCode, err := jsonparser.GetInt(fileData, "expectedReturnCode")
-	assert.NoError(t, err, "Error jsonparsing root.code from file %s. Desc: %v.", testFile, err)
-
-	// Get both bid response and error message, if any
-	parsedTestData.ExpectedBidResponse, _, _, err = jsonparser.Get(fileData, "expectedBidResponse")
-	parsedTestData.ExpectedErrorMessage, errEm = jsonparser.GetString(fileData, "expectedErrorMessage")
-
-	assert.Falsef(t, (err == nil && errEm == nil), "Test case file can't have both a valid expectedBidResponse and a valid expectedErrorMessage, fields are mutually exclusive")
-	assert.Falsef(t, (err != nil && errEm != nil), "Test case file should come with either a valid expectedBidResponse or a valid expectedErrorMessage, not both.")
-
-	parsedTestData.ExpectedReturnCode = int(parsedReturnCode)
-
-	return parsedTestData
-}
-
-func (tc *testConfigValues) getBlacklistedAppMap() map[string]bool {
-	var blacklistedAppMap map[string]bool
-
-	if len(tc.BlacklistedApps) > 0 {
-		blacklistedAppMap = make(map[string]bool, len(tc.BlacklistedApps))
-		for _, app := range tc.BlacklistedApps {
-			blacklistedAppMap[app] = true
-		}
-	}
-	return blacklistedAppMap
-}
-
-func (tc *testConfigValues) getBlackListedAccountMap() map[string]bool {
-	var blacklistedAccountMap map[string]bool
-
-	if len(tc.BlacklistedAccounts) > 0 {
-		blacklistedAccountMap = make(map[string]bool, len(tc.BlacklistedAccounts))
-		for _, account := range tc.BlacklistedAccounts {
-			blacklistedAccountMap[account] = true
-		}
-	}
-	return blacklistedAccountMap
-}
-
-func (tc *testConfigValues) getAdaptersConfigMap() map[string]config.Adapter {
-	var adaptersConfig map[string]config.Adapter
-
-	if len(tc.DisabledAdapters) > 0 {
-		adaptersConfig = make(map[string]config.Adapter, len(tc.DisabledAdapters))
-		for _, adapterName := range tc.DisabledAdapters {
-			adaptersConfig[adapterName] = config.Adapter{Disabled: true}
-		}
-	}
-	return adaptersConfig
-}
-
 // Once unmarshalled, bidResponse objects can't simply be compared with an `assert.Equalf()` call
 // because tests fail if the elements inside the `bidResponse.SeatBid` and `bidResponse.SeatBid.Bid`
 // arrays, if any, are not listed in the exact same order in the actual version and in the expected version.
@@ -262,12 +180,10 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o
 
 	//Assert non-array BidResponse fields
 	assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile)
-	assert.Equalf(t, expectedBidResponse.BidID, actualBidResponse.BidID, "BidResponse.BidID doesn't match expected. Test: %s\n", testFile)
-	assert.Equalf(t, expectedBidResponse.NBR, actualBidResponse.NBR, "BidResponse.NBR doesn't match expected. Test: %s\n", testFile)
 	assert.Equalf(t, expectedBidResponse.Cur, actualBidResponse.Cur, "BidResponse.Cur doesn't match expected. Test: %s\n", testFile)
 
 	//Assert []SeatBid and their Bid elements independently of their order
-	assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid array doesn't match expected. Test: %s\n", testFile)
+	assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid is expected to contain %d elements but contains %d. Test: %s\n", len(expectedBidResponse.SeatBid), len(actualBidResponse.SeatBid), testFile)
 
 	//Given that bidResponses have the same length, compare them in an order-independent way using maps
 	var actualSeatBidsMap map[string]openrtb2.SeatBid = make(map[string]openrtb2.SeatBid, 0)
@@ -280,15 +196,36 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o
 		expectedSeatBidsMap[seatBid.Seat] = seatBid
 	}
 
-	for k, expectedSeatBid := range expectedSeatBidsMap {
+	for bidderName, expectedSeatBid := range expectedSeatBidsMap {
+		if !assert.Contains(t, actualSeatBidsMap, bidderName, "BidResponse.SeatBid[%s] was not found as expected. Test: %s\n", bidderName, testFile) {
+			continue
+		}
+
 		//Assert non-array SeatBid fields
-		assert.Equalf(t, expectedSeatBid.Seat, actualSeatBidsMap[k].Seat, "actualSeatBidsMap[%s].Seat doesn't match expected. Test: %s\n", k, testFile)
-		assert.Equalf(t, expectedSeatBid.Group, actualSeatBidsMap[k].Group, "actualSeatBidsMap[%s].Group doesn't match expected. Test: %s\n", k, testFile)
-		assert.Equalf(t, expectedSeatBid.Ext, actualSeatBidsMap[k].Ext, "actualSeatBidsMap[%s].Ext doesn't match expected. Test: %s\n", k, testFile)
-		assert.Len(t, actualSeatBidsMap[k].Bid, len(expectedSeatBid.Bid), "BidResponse.SeatBid[].Bid array doesn't match expected. Test: %s\n", testFile)
+		assert.Equalf(t, expectedSeatBid.Seat, actualSeatBidsMap[bidderName].Seat, "actualSeatBidsMap[%s].Seat doesn't match expected. Test: %s\n", bidderName, testFile)
+		assert.Equalf(t, expectedSeatBid.Group, actualSeatBidsMap[bidderName].Group, "actualSeatBidsMap[%s].Group doesn't match expected. Test: %s\n", bidderName, testFile)
+		assert.Equalf(t, expectedSeatBid.Ext, actualSeatBidsMap[bidderName].Ext, "actualSeatBidsMap[%s].Ext doesn't match expected. Test: %s\n", bidderName, testFile)
+
+		// Assert Bid arrays
+		assert.Len(t, actualSeatBidsMap[bidderName].Bid, len(expectedSeatBid.Bid), "BidResponse.SeatBid[].Bid array is expected to contain %d elements but has %d. Test: %s\n", len(expectedSeatBid.Bid), len(actualSeatBidsMap[bidderName].Bid), testFile)
+		// Given that actualSeatBidsMap[bidderName].Bid and expectedSeatBid.Bid have the same length, compare them in an order-independent way using maps
+		var expectedBidMap map[string]openrtb2.Bid = make(map[string]openrtb2.Bid, 0)
+		for _, bid := range expectedSeatBid.Bid {
+			expectedBidMap[bid.ID] = bid
+		}
+
+		var actualBidMap map[string]openrtb2.Bid = make(map[string]openrtb2.Bid, 0)
+		for _, bid := range actualSeatBidsMap[bidderName].Bid {
+			actualBidMap[bid.ID] = bid
+		}
 
-		//Assert Bid arrays
-		assert.ElementsMatch(t, expectedSeatBid.Bid, actualSeatBidsMap[k].Bid, "BidResponse.SeatBid array doesn't match expected. Test: %s\n", testFile)
+		for bidID, expectedBid := range expectedBidMap {
+			if !assert.Contains(t, actualBidMap, bidID, "BidResponse.SeatBid[%s].Bid[%s].ID doesn't match expected. Test: %s\n", bidderName, bidID, testFile) {
+				continue
+			}
+			assert.Equalf(t, expectedBid.ImpID, actualBidMap[bidID].ImpID, "BidResponse.SeatBid[%s].Bid[%s].ImpID doesn't match expected. Test: %s\n", bidderName, bidID, testFile)
+			assert.Equalf(t, expectedBid.Price, actualBidMap[bidID].Price, "BidResponse.SeatBid[%s].Bid[%s].Price doesn't match expected. Test: %s\n", bidderName, bidID, testFile)
+		}
 	}
 }
 
@@ -428,7 +365,7 @@ func TestExplicitUserId(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		ex,
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		cfg,
@@ -454,58 +391,6 @@ func TestExplicitUserId(t *testing.T) {
 	}
 }
 
-func doRequest(t *testing.T, test testCase) (int, string) {
-	bidderInfos := getBidderInfos(test.Config.getAdaptersConfigMap(), openrtb_ext.CoreBidderNames())
-	bidderMap := exchange.GetActiveBidders(bidderInfos)
-	disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos)
-
-	mockExchange := newMockBidExchange(test.Config.MockBidder, test.Config.CurrencyRates)
-
-	endpoint, _ := NewEndpoint(
-		fakeUUIDGenerator{},
-		mockExchange,
-		newParamsValidator(t),
-		&mockStoredReqFetcher{},
-		empty_fetcher.EmptyFetcher{},
-		&config.Configuration{
-			MaxRequestSize:     maxSize,
-			BlacklistedApps:    test.Config.BlacklistedApps,
-			BlacklistedAppMap:  test.Config.getBlacklistedAppMap(),
-			BlacklistedAccts:   test.Config.BlacklistedAccounts,
-			BlacklistedAcctMap: test.Config.getBlackListedAccountMap(),
-			AccountRequired:    test.Config.AccountRequired,
-		},
-		&metricsConfig.NilMetricsEngine{},
-		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
-		disabledBidders,
-		[]byte(test.Config.AliasJSON),
-		bidderMap,
-		empty_fetcher.EmptyFetcher{})
-
-	request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(test.BidRequest))
-	recorder := httptest.NewRecorder()
-	endpoint(recorder, request, nil) //Request comes from the unmarshalled mockBidRequest
-	return recorder.Code, recorder.Body.String()
-}
-
-// fetchFiles returns a list of the files from dir, or fails the test if an error occurs.
-func fetchFiles(t *testing.T, dir string) []os.FileInfo {
-	t.Helper()
-	requestFiles, err := ioutil.ReadDir(dir)
-	if err != nil {
-		t.Fatalf("Failed to read folder: %s", dir)
-	}
-	return requestFiles
-}
-
-func readFile(t *testing.T, filename string) []byte {
-	data, err := ioutil.ReadFile(filename)
-	if err != nil {
-		t.Fatalf("Failed to read file %s: %v", filename, err)
-	}
-	return data
-}
-
 // TestBadAliasRequests() reuses two requests that would fail anyway.  Here, we
 // take advantage of our knowledge that processStoredRequests() in auction.go
 // processes aliases before it processes stored imps.  Changing that order
@@ -536,7 +421,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -588,7 +473,7 @@ func TestNilExchange(t *testing.T) {
 	_, err := NewEndpoint(
 		fakeUUIDGenerator{},
 		nil,
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -633,7 +518,7 @@ func TestExchangeError(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		&brokenExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -757,7 +642,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) {
 		endpoint, _ := NewEndpoint(
 			fakeUUIDGenerator{},
 			exchange,
-			newParamsValidator(t),
+			mockBidderParamValidator{},
 			&mockStoredReqFetcher{},
 			empty_fetcher.EmptyFetcher{},
 			cfg,
@@ -953,7 +838,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) {
 		endpoint, _ := NewEndpoint(
 			fakeUUIDGenerator{},
 			exchange,
-			newParamsValidator(t),
+			mockBidderParamValidator{},
 			&mockStoredReqFetcher{},
 			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
@@ -1108,7 +993,7 @@ func TestStoredRequests(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1447,13 +1332,11 @@ func TestAddMissingReqExtParamsInImpExt(t *testing.T) {
 	}
 }
 
-//impExtByBidder map[string]json.RawMessage, reqExtParams map[string]map[string]json.RawMessage) (bool, error) {
-
 func TestValidateRequest(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1756,7 +1639,7 @@ func TestSetIntegrationType(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1821,7 +1704,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{id: "foo", err: nil},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1921,7 +1804,7 @@ func TestOversizedRequest(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1958,7 +1841,7 @@ func TestRequestSizeEdgeCase(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1994,7 +1877,7 @@ func TestNoEncoding(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		&mockExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -2071,7 +1954,7 @@ func TestContentType(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		&mockExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -2285,7 +2168,7 @@ func TestValidateImpExt(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2333,7 +2216,7 @@ func TestCurrencyTrunc(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2379,7 +2262,7 @@ func TestCCPAInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2429,7 +2312,7 @@ func TestNoSaleInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2482,7 +2365,7 @@ func TestValidateSourceTID(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2525,7 +2408,7 @@ func TestSChainInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2874,7 +2757,7 @@ func TestEidPermissionsInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -3127,7 +3010,7 @@ func TestIOS14EndToEnd(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		exchange,
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -3156,7 +3039,7 @@ func TestAuctionWarnings(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&warningsCheckExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -3197,7 +3080,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&warningsCheckExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -3673,7 +3556,7 @@ func TestAuctionResponseHeaders(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		exchange,
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -3698,889 +3581,10 @@ func TestAuctionResponseHeaders(t *testing.T) {
 	}
 }
 
-// warningsCheckExchange is a well-behaved exchange which stores all incoming warnings.
-type warningsCheckExchange struct {
-	auctionRequest exchange.AuctionRequest
-}
-
-func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
-	e.auctionRequest = r
-	return nil, nil
-}
-
-// nobidExchange is a well-behaved exchange which always bids "no bid".
-type nobidExchange struct {
-	gotRequest *openrtb2.BidRequest
-}
-
-func (e *nobidExchange) HoldAuction(ctx context.Context, auctionRequest exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
-	r := auctionRequest.BidRequestWrapper
-	e.gotRequest = r.BidRequest
-	return &openrtb2.BidResponse{
-		ID:    r.BidRequest.ID,
-		BidID: "test bid id",
-		NBR:   openrtb2.NoBidReasonCodeUnknownError.Ptr(),
-	}, nil
-}
-
-type mockBidExchange struct {
-	mockBidder mockBidExchangeBidder
-	pbsRates   map[string]map[string]float64
-}
-
-func newMockBidExchange(bidder mockBidExchangeBidder, mockCurrencyConversionRates map[string]map[string]float64) *mockBidExchange {
-	if bidder.BidCurrency == "" {
-		bidder.BidCurrency = "USD"
-	}
-
-	return &mockBidExchange{
-		mockBidder: bidder,
-		pbsRates:   mockCurrencyConversionRates,
-	}
-}
-
-// getAuctionCurrencyRates copies the logic of the exchange package for testing purposes
-func (e *mockBidExchange) getAuctionCurrencyRates(customRates *openrtb_ext.ExtRequestCurrency) currency.Conversions {
-	if customRates == nil {
-		// The timestamp is required for the function signature, but is not used and its
-		// value has no significance in the tests
-		return currency.NewRates(e.pbsRates)
-	}
-
-	usePbsRates := true
-	if customRates.UsePBSRates != nil {
-		usePbsRates = *customRates.UsePBSRates
-	}
-
-	if !usePbsRates {
-		// The timestamp is required for the function signature, but is not used and its
-		// value has no significance in the tests
-		return currency.NewRates(customRates.ConversionRates)
-	}
-
-	// Both PBS and custom rates can be used, check if ConversionRates is not empty
-	if len(customRates.ConversionRates) == 0 {
-		// Custom rates map is empty, use PBS rates only
-		return currency.NewRates(e.pbsRates)
-	}
-
-	// Return an AggregateConversions object that includes both custom and PBS currency rates but will
-	// prioritize custom rates over PBS rates whenever a currency rate is found in both
-	return currency.NewAggregateConversions(currency.NewRates(customRates.ConversionRates), currency.NewRates(e.pbsRates))
-}
-
-// mockBidExchange is a well-behaved exchange that lists the bidders found in every bidRequest.Imp[i].Ext
-// into the bidResponse.Ext to assert the bidder adapters that were not filtered out in the validation process
-func (e *mockBidExchange) HoldAuction(ctx context.Context, auctionRequest exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
-	r := auctionRequest.BidRequestWrapper
-	bidResponse := &openrtb2.BidResponse{
-		ID:    r.BidRequest.ID,
-		BidID: "test bid id",
-		NBR:   openrtb2.NoBidReasonCodeUnknownError.Ptr(),
-	}
-
-	// Use currencies inside r.BidRequest.Cur, if any, and convert currencies if needed
-	if len(r.BidRequest.Cur) == 0 {
-		r.BidRequest.Cur = []string{"USD"}
-	}
-
-	var currencyFrom string = e.mockBidder.getBidCurrency()
-	var conversionRate float64 = 0.00
-	var err error
-
-	var requestExt openrtb_ext.ExtRequest
-	if len(r.BidRequest.Ext) > 0 {
-		if err := json.Unmarshal(r.BidRequest.Ext, &requestExt); err != nil {
-			return nil, fmt.Errorf("request.ext is invalid: %v", err)
-		}
-	}
-
-	conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions)
-	for _, bidReqCur := range r.BidRequest.Cur {
-		if conversionRate, err = conversions.GetRate(currencyFrom, bidReqCur); err == nil {
-			bidResponse.Cur = bidReqCur
-			break
-		}
-	}
-
-	if conversionRate == 0 {
-		// Can't have bids if there's not even a 1 USD to 1 USD conversion rate
-		return nil, errors.New("Can't produce bid with no valid currency to use or currency conversion to convert to.")
-	}
-
-	if len(r.BidRequest.Imp) > 0 {
-		var SeatBidMap = make(map[string]openrtb2.SeatBid, 0)
-		for _, imp := range r.BidRequest.Imp {
-			var bidderExts map[string]json.RawMessage
-			if err := json.Unmarshal(imp.Ext, &bidderExts); err != nil {
-				return nil, err
-			}
-
-			if rawPrebidExt, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok {
-				var prebidExt openrtb_ext.ExtImpPrebid
-				if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil && prebidExt.Bidder != nil {
-					for bidder, ext := range prebidExt.Bidder {
-						if ext == nil {
-							continue
-						}
-
-						bidderExts[bidder] = ext
-					}
-				}
-			}
-
-			for bidderNameOrAlias := range bidderExts {
-				if isBidderToValidate(bidderNameOrAlias) {
-					if val, ok := SeatBidMap[bidderNameOrAlias]; ok {
-						val.Bid = append(val.Bid, openrtb2.Bid{ID: e.mockBidder.getBidId(bidderNameOrAlias)})
-					} else {
-						SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{
-							Seat: e.mockBidder.getSeatName(bidderNameOrAlias),
-							Bid: []openrtb2.Bid{
-								{
-									ID:    e.mockBidder.getBidId(bidderNameOrAlias),
-									Price: e.mockBidder.getBidPrice() * conversionRate,
-								},
-							},
-						}
-					}
-				}
-			}
-		}
-		for _, seatBid := range SeatBidMap {
-			bidResponse.SeatBid = append(bidResponse.SeatBid, seatBid)
-		}
-	}
-
-	return bidResponse, nil
-}
-
-type mockBidExchangeBidder struct {
-	BidCurrency string  `json:"currency"`
-	BidPrice    float64 `json:"price"`
-}
-
-func (bidder mockBidExchangeBidder) getBidCurrency() string {
-	return bidder.BidCurrency
-}
-func (bidder mockBidExchangeBidder) getBidPrice() float64 {
-	return bidder.BidPrice
-}
-func (bidder mockBidExchangeBidder) getSeatName(bidderNameOrAlias string) string {
-	return fmt.Sprintf("%s-bids", bidderNameOrAlias)
-}
-func (bidder mockBidExchangeBidder) getBidId(bidderNameOrAlias string) string {
-	return fmt.Sprintf("%s-bid", bidderNameOrAlias)
-}
-
-type brokenExchange struct{}
-
-func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
-	return nil, errors.New("Critical, unrecoverable error.")
-}
-
 // StoredRequest testing
 
 // Test stored request data
 
-// Stored Requests
-// first below is valid JSON
-// second below is identical to first but with extra '}' for invalid JSON
-var testStoredRequestData = map[string]json.RawMessage{
-	"1": json.RawMessage(`{"id": "{{UUID}}"}`),
-	"2": json.RawMessage(`{
-		"id": "{{uuid}}",
-		"tmax": 500,
-		"ext": {
-			"prebid": {
-				"targeting": {
-					"pricegranularity": "low"
-				}
-			}
-		}
-	}`),
-	"3": json.RawMessage(`{
-		"tmax": 500,
-				"ext": {
-						"prebid": {
-								"targeting": {
-										"pricegranularity": "low"
-								}
-						}
-				}}
-		}`),
-	"4": json.RawMessage(`{"id": "ThisID", "cur": ["USD"]}`),
-}
-
-// Stored Imp Requests
-// first below has valid JSON but doesn't match schema
-// second below has invalid JSON (missing comma after rubicon accountId entry) but otherwise matches schema
-// third below has valid JSON and matches schema
-var testStoredImpData = map[string]json.RawMessage{
-	"1": json.RawMessage(`{
-			"id": "adUnit1",
-			"ext": {
-				"appnexus": {
-					"placementId": "abc",
-					"position": "above",
-					"reserve": 0.35
-				},
-				"rubicon": {
-					"accountId": "abc"
-				}
-			},
-			"video":{
-				"w":200,
-				"h":300
-			}
-		}`),
-	"2": json.RawMessage(`{
-			"id": "adUnit1",
-			"ext": {
-				"appnexus": {
-					"placementId": "abc",
-					"position": "above",
-					"reserve": 0.35
-				},
-				"rubicon": {
-					"accountId": "abc"
-				}
-			}
-		}`),
-	"7": json.RawMessage(`{
-			"id": "adUnit1",
-			"ext": {
-				"appnexus": {
-					"placementId": 12345678,
-					"position": "above",
-					"reserve": 0.35
-				},
-				"rubicon": {
-					"accountId": 23456789
-					"siteId": 113932,
-					"zoneId": 535510
-				}
-			}
-		}`),
-	"9": json.RawMessage(`{
-"id": "adUnit1",
-			"ext": {
-				"appnexus": {
-					"placementId": 12345678,
-					"position": "above",
-					"reserve": 0.35
-				},
-				"rubicon": {
-					"accountId": 23456789,
-					"siteId": 113932,
-					"zoneId": 535510
-				}
-			}
-		}`),
-	"10": json.RawMessage(`{
-			"ext": {
-				"appnexus": {
-					"placementId": 12345678,
-					"position": "above",
-					"reserve": 0.35
-				}
-			}
-		}`),
-}
-
-// Incoming requests with stored request IDs
-var testStoredRequests = []string{
-	`{
-		"id": "ThisID",
-		"imp": [
-			{
-				"video":{
-					"h":300,
-					"w":200
-				},
-				"ext": {
-					"prebid": {
-						"storedrequest": {
-							"id": "1"
-						},
-						"options": {
-							"echovideoattrs": true
-						}
-					}
-				}
-			}
-		],
-		"ext": {
-			"prebid": {
-				"cache": {
-					"markup": 1
-				},
-				"targeting": {
-				}
-			}
-		}
-	}`,
-	`{
-		"id": "ThisID",
-		"imp": [
-			{
-				"id": "adUnit2",
-				"ext": {
-					"prebid": {
-						"storedrequest": {
-							"id": "1"
-						},
-						"options": {
-							"echovideoattrs": true
-						}
-					},
-					"appnexus": {
-						"placementId": "def",
-						"trafficSourceCode": "mysite.com",
-						"reserve": null
-					},
-					"rubicon": null
-				}
-			}
-		],
-		"ext": {
-			"prebid": {
-				"cache": {
-					"markup": 1
-				},
-				"targeting": {
-				}
-			}
-		}
-	}`,
-	`{
-		"id": "ThisID",
-		"imp": [
-			{
-				"ext": {
-					"prebid": {
-						"storedrequest": {
-							"id": "2"
-						},
-						"options": {
-							"echovideoattrs": false
-						}
-					}
-				}
-			}
-		],
-		"ext": {
-			"prebid": {
-				"storedrequest": {
-					"id": "2"
-				}
-			}
-		}
-	}`,
-	`{
-		"id": "ThisID",
-		"imp": [
-			{
-				"id": "some-static-imp",
-				"video":{
-					"mimes":["video/mp4"]
-				},
-				"ext": {
-					"appnexus": {
-						"placementId": "abc",
-						"position": "below"
-					}
-				}
-			},
-			{
-				"ext": {
-					"prebid": {
-						"storedrequest": {
-							"id": "1"
-						}
-					}
-				}
-			}
-		],
-		"ext": {
-			"prebid": {
-				"cache": {
-					"markup": 1
-				},
-				"targeting": {
-				}
-			}
-		}
-	}`,
-}
-
-// The expected requests after stored request processing
-var testFinalRequests = []string{
-	`{
-		"id": "ThisID",
-		"imp": [
-			{
-				"video":{
-					"h":300,
-					"w":200
-				},
-				"ext":{
-					"appnexus":{
-						"placementId":"abc",
-						"position":"above",
-						"reserve":0.35
-					},
-					"prebid":{
-						"storedrequest":{
-							"id":"1"
-						},
-					"options":{
-						"echovideoattrs":true
-					}
-				},
-				"rubicon":{
-					"accountId":"abc"
-				}
-			},
-			"id":"adUnit1"
-			}
-		],
-		"ext": {
-			"prebid": {
-				"cache": {
-					"markup": 1
-				},
-				"targeting": {
-			}
-		}
-}
-	}`,
-	`{
-		"id": "ThisID",
-		"imp": [
-			{
-				"video":{
-					"w":200,
-					"h":300
-				},
-				"ext":{
-					"appnexus":{
-						"placementId":"def",
-						"position":"above",
-						"trafficSourceCode":"mysite.com"
-					},
-					"prebid":{
-						"storedrequest":{
-							"id":"1"
-						},
-						"options":{
-							"echovideoattrs":true
-						}
-					}
-				},
-				"id":"adUnit2"
-			}
-		],
-		"ext": {
-			"prebid": {
-				"cache": {
-					"markup": 1
-				},
-				"targeting": {
-				}
-			}
-		}
-	}`,
-	`{
-  		"ext": {
-  		  "prebid": {
-  		    "storedrequest": {
-  		      "id": "2"
-  		    },
-  		    "targeting": {
-  		      "pricegranularity": "low"
-  		    }
-  		  }
-  		},
-  		"id": "ThisID",
-  		"imp": [
-  		  {
-  		    "ext": {
-  		      "appnexus": {
-  		        "placementId": "abc",
-  		        "position": "above",
-  		        "reserve": 0.35
-  		      },
-  		      "prebid": {
-  		        "storedrequest": {
-  		          "id": "2"
-  		        },
-  		        "options":{
-					"echovideoattrs":false
-				}
-  		      },
-  		      "rubicon": {
-  		        "accountId": "abc"
-  		      }
-  		    },
-  		    "id": "adUnit1"
-  		  }
-  		],
-  		"tmax": 500
-	}
-`,
-	`{
-	"id": "ThisID",
-	"imp": [
-		{
-    		"id": "some-static-imp",
-    		"video": {
-    		  "mimes": [
-    		    "video/mp4"
-    		  ]
-    		},
-    		"ext": {
-    		  "appnexus": {
-    		    "placementId": "abc",
-    		    "position": "below"
-    		  }
-    		}
-  		},
-  		{
-  		  "ext": {
-  		    "appnexus": {
-  		      "placementId": "abc",
-  		      "position": "above",
-  		      "reserve": 0.35
-  		    },
-  		    "prebid": {
-  		      "storedrequest": {
-  		        "id": "1"
-  		      }
-  		    },
-  		    "rubicon": {
-  		      "accountId": "abc"
-  		    }
-  		  },
-  		  "id": "adUnit1",
-		  "video":{
-				"w":200,
-				"h":300
-          }
-  		}
-	],
-	"ext": {
-		"prebid": {
-			"cache": {
-				"markup": 1
-			},
-			"targeting": {
-			}
-		}
-	}
-}`,
-}
-
-var testStoredImpIds = []string{
-	"adUnit1", "adUnit2", "adUnit1", "some-static-imp",
-}
-
-var testStoredImps = []string{
-	`{
-		"id": "adUnit1",
-        "ext": {
-        	"appnexus": {
-        		"placementId": "abc",
-        		"position": "above",
-        		"reserve": 0.35
-        	},
-        	"rubicon": {
-        		"accountId": "abc"
-        	}
-        },
-		"video":{
-        	"w":200,
-        	"h":300
-		}
-	}`,
-	`{
-		"id": "adUnit1",
-        "ext": {
-        	"appnexus": {
-        		"placementId": "abc",
-        		"position": "above",
-        		"reserve": 0.35
-        	},
-        	"rubicon": {
-        		"accountId": "abc"
-        	}
-        },
-		"video":{
-        	"w":200,
-        	"h":300
-		}
-	}`,
-	`{
-			"id": "adUnit1",
-			"ext": {
-				"appnexus": {
-					"placementId": "abc",
-					"position": "above",
-					"reserve": 0.35
-				},
-				"rubicon": {
-					"accountId": "abc"
-				}
-			}
-		}`,
-	``,
-}
-
-var testBidRequests = []string{
-	`{
-		"id": "ThisID",
-		"app": {
-			"id": "123"
-		},
-		"imp": [
-			{
-				"video":{
-					"h":300,
-					"w":200
-				},
-				"ext": {
-					"prebid": {
-						"storedrequest": {
-							"id": "1"
-						},
-						"options": {
-							"echovideoattrs": true
-						}
-					}
-				}
-			}
-		],
-		"ext": {
-			"prebid": {
-				"cache": {
-					"markup": 1
-				},
-				"targeting": {
-				}
-			}
-		}
-	}`,
-	`{
-		"id": "ThisID",
-		"site": {
-			"page": "prebid.org"
-		},
-		"imp": [
-			{
-				"id": "adUnit2",
-				"ext": {
-					"prebid": {
-						"storedrequest": {
-							"id": "1"
-						},
-						"options": {
-							"echovideoattrs": true
-						}
-					},
-					"appnexus": {
-						"placementId": "def",
-						"trafficSourceCode": "mysite.com",
-						"reserve": null
-					},
-					"rubicon": null
-				}
-			}
-		],
-		"ext": {
-			"prebid": {
-				"storedrequest": {
-					"id": "1"
-				}
-			}
-		}
-	}`,
-	`{
-		"id": "ThisID",
-		"app": {
-			"id": "123"
-		},
-		"imp": [
-			{
-				"ext": {
-					"prebid": {
-						"storedrequest": {
-							"id": "2"
-						},
-						"options": {
-							"echovideoattrs": false
-						}
-					}
-				}
-			}
-		],
-		"ext": {
-			"prebid": {
-				"storedrequest": {
-					"id": "2"
-				}
-			}
-		}
-	}`,
-	`{
-		"id": "ThisID",
-		"site": {
-			"page": "prebid.org"
-		},
-		"imp": [
-			{
-				"ext": {
-					"prebid": {
-						"storedrequest": {
-							"id": "2"
-						},
-						"options": {
-							"echovideoattrs": false
-						}
-					}
-				}
-			}
-		],
-		"ext": {
-			"prebid": {
-				"storedrequest": {
-					"id": "2"
-				}
-			}
-		}
-	}`,
-	`{
-		"id": "ThisID",
-		"app": {
-			"id": "123"
-		},
-		"imp": [
-			{
-				"ext": {
-					"prebid": {
-						"storedrequest": {
-							"id": "1"
-						},
-						"options": {
-							"echovideoattrs": false
-						}
-					}
-				}
-			}
-		],
-		"ext": {
-			"prebid": {
-				"storedrequest": {
-					"id": "1"
-				}
-			}
-		}
-	}`,
-	`{
-		"id": "ThisID",
-		"imp": [{
-			"id": "some-impression-id",
-			"banner": {
-				"format": [{
-						"w": 600,
-						"h": 500
-					},
-					{
-						"w": 300,
-						"h": 600
-					}
-				]
-			},
-			"ext": {
-				"appnexus": {
-					"placementId": 12883451
-				}
-			}
-		}],
-		"ext": {
-			"prebid": {
-				"debug": true,
-				"storedrequest": {
-					"id": "4"
-				}
-			}
-		},
-	  "site": {
-		"page": "https://example.com"
-	  }
-	}`,
-}
-
-type mockStoredReqFetcher struct {
-}
-
-func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
-	return testStoredRequestData, testStoredImpData, nil
-}
-
-func (cf mockStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
-	return nil, nil
-}
-
-type mockExchange struct {
-	lastRequest *openrtb2.BidRequest
-}
-
-func (m *mockExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
-	m.lastRequest = r.BidRequestWrapper.BidRequest
-	return &openrtb2.BidResponse{
-		SeatBid: []openrtb2.SeatBid{{
-			Bid: []openrtb2.Bid{{
-				AdM: "<script></script>",
-			}},
-		}},
-	}, nil
-}
-
-func getBidderInfos(cfg map[string]config.Adapter, biddersNames []openrtb_ext.BidderName) config.BidderInfos {
-	biddersInfos := make(config.BidderInfos)
-	for _, name := range biddersNames {
-		adapterConfig, ok := cfg[string(name)]
-		if !ok {
-			adapterConfig = config.Adapter{}
-		}
-		biddersInfos[string(name)] = newBidderInfo(adapterConfig)
-	}
-	return biddersInfos
-}
-
-func newBidderInfo(cfg config.Adapter) config.BidderInfo {
-	return config.BidderInfo{
-		Enabled: !cfg.Disabled,
-	}
-}
-
-type hardcodedResponseIPValidator struct {
-	response bool
-}
-
-func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool {
-	return v.response
-}
-
-type fakeUUIDGenerator struct {
-	id  string
-	err error
-}
-
-func (f fakeUUIDGenerator) Generate() (string, error) {
-	return f.id, f.err
-}
-
 func TestValidateBanner(t *testing.T) {
 	impIndex := 0
 
@@ -4649,7 +3653,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 			deps := &endpointDeps{
 				fakeUUIDGenerator{},
 				&warningsCheckExchange{},
-				newParamsValidator(t),
+				mockBidderParamValidator{},
 				&mockStoredReqFetcher{},
 				empty_fetcher.EmptyFetcher{},
 				empty_fetcher.EmptyFetcher{},
@@ -4749,7 +3753,7 @@ func TestParseRequestStoredResponses(t *testing.T) {
 			deps := &endpointDeps{
 				fakeUUIDGenerator{},
 				&warningsCheckExchange{},
-				newParamsValidator(t),
+				mockBidderParamValidator{},
 				&mockStoredReqFetcher{},
 				empty_fetcher.EmptyFetcher{},
 				empty_fetcher.EmptyFetcher{},
@@ -4836,7 +3840,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
 			deps := &endpointDeps{
 				fakeUUIDGenerator{},
 				&warningsCheckExchange{},
-				newParamsValidator(t),
+				mockBidderParamValidator{},
 				&mockStoredReqFetcher{},
 				empty_fetcher.EmptyFetcher{},
 				empty_fetcher.EmptyFetcher{},
@@ -4869,7 +3873,7 @@ func TestValidateStoredResp(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json
index d592cb66..8a7e1ed2 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json
@@ -36,11 +36,11 @@
         "bid": [
           {
             "id": "appnexus-bid",
-            "impid": "",
+            "impid": "some-impression-id",
             "price": 0
           }
         ],
-        "seat": "appnexus-bids"
+        "seat": "appnexus"
       }
     ],
     "bidid": "test bid id",
diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go
new file mode 100644
index 00000000..d42ba22f
--- /dev/null
+++ b/endpoints/openrtb2/test_utils.go
@@ -0,0 +1,1256 @@
+package openrtb2
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/http/httptest"
+	"path/filepath"
+	"strconv"
+	"testing"
+	"time"
+
+	"github.com/buger/jsonparser"
+	"github.com/julienschmidt/httprouter"
+	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/analytics"
+	analyticsConf "github.com/prebid/prebid-server/analytics/config"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/currency"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/exchange"
+	"github.com/prebid/prebid-server/gdpr"
+	"github.com/prebid/prebid-server/metrics"
+	metricsConfig "github.com/prebid/prebid-server/metrics/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	pbc "github.com/prebid/prebid-server/prebid_cache_client"
+	"github.com/prebid/prebid-server/stored_requests"
+	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
+	"github.com/prebid/prebid-server/util/iputil"
+	"github.com/prebid/prebid-server/util/uuidutil"
+)
+
+// In this file we define:
+//  - Auxiliary types
+//  - Unit test interface implementations such as mocks
+//  - Other auxiliary functions that don't make assertions and don't take t *testing.T as parameter
+//
+// All of the above are useful for this package's unit test framework.
+
+// ----------------------
+// test auxiliary types
+// ----------------------
+const maxSize = 1024 * 256
+
+const (
+	AMP_ENDPOINT = iota
+	OPENRTB_ENDPOINT
+	VIDEO_ENDPOINT
+)
+
+type testCase struct {
+	// Common
+	endpointType         int
+	Description          string            `json:"description"`
+	Config               *testConfigValues `json:"config"`
+	BidRequest           json.RawMessage   `json:"mockBidRequest"`
+	ExpectedReturnCode   int               `json:"expectedReturnCode,omitempty"`
+	ExpectedErrorMessage string            `json:"expectedErrorMessage"`
+
+	// "/openrtb2/auction" endpoint JSON test info
+	ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"`
+
+	// "/openrtb2/amp" endpoint JSON test info
+	storedRequest       map[string]json.RawMessage `json:"mockAmpStoredRequest"`
+	StoredResponse      map[string]json.RawMessage `json:"mockAmpStoredResponse"`
+	ExpectedAmpResponse AmpResponse                `json:"expectedAmpResponse"`
+}
+
+type testConfigValues struct {
+	AccountRequired     bool                          `json:"accountRequired"`
+	AliasJSON           string                        `json:"aliases"`
+	BlacklistedAccounts []string                      `json:"blacklistedAccts"`
+	BlacklistedApps     []string                      `json:"blacklistedApps"`
+	DisabledAdapters    []string                      `json:"disabledAdapters"`
+	CurrencyRates       map[string]map[string]float64 `json:"currencyRates"`
+	MockBidders         []mockBidderHandler           `json:"mockBidders"`
+	RealParamsValidator bool                          `json:"realParamsValidator"`
+}
+
+type brokenExchange struct{}
+
+func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+	return nil, errors.New("Critical, unrecoverable error.")
+}
+
+// Stored Requests
+var testStoredRequestData = map[string]json.RawMessage{
+	// Valid JSON
+	"1": json.RawMessage(`{"id": "{{UUID}}"}`),
+	"2": json.RawMessage(`{
+		"id": "{{uuid}}",
+		"tmax": 500,
+		"ext": {
+			"prebid": {
+				"targeting": {
+					"pricegranularity": "low"
+				}
+			}
+		}
+	}`),
+	// Invalid JSON because it comes with an extra closing curly brace '}'
+	"3": json.RawMessage(`{
+		"tmax": 500,
+				"ext": {
+						"prebid": {
+								"targeting": {
+										"pricegranularity": "low"
+								}
+						}
+				}}
+		}`),
+	// Valid JSON
+	"4": json.RawMessage(`{"id": "ThisID", "cur": ["USD"]}`),
+}
+
+// Stored Imp Requests
+var testStoredImpData = map[string]json.RawMessage{
+	// Has valid JSON and matches schema
+	"1": json.RawMessage(`{
+			"id": "adUnit1",
+			"ext": {
+				"appnexus": {
+					"placementId": "abc",
+					"position": "above",
+					"reserve": 0.35
+				},
+				"rubicon": {
+					"accountId": "abc"
+				}
+			},
+			"video":{
+				"w":200,
+				"h":300
+			}
+		}`),
+	// Has valid JSON, matches schema but is missing video object
+	"2": json.RawMessage(`{
+			"id": "adUnit1",
+			"ext": {
+				"appnexus": {
+					"placementId": "abc",
+					"position": "above",
+					"reserve": 0.35
+				},
+				"rubicon": {
+					"accountId": "abc"
+				}
+			}
+		}`),
+	// Invalid JSON, is missing a coma after the rubicon's "accountId" field
+	"7": json.RawMessage(`{
+			"id": "adUnit1",
+			"ext": {
+				"appnexus": {
+					"placementId": 12345678,
+					"position": "above",
+					"reserve": 0.35
+				},
+				"rubicon": {
+					"accountId": 23456789
+					"siteId": 113932,
+					"zoneId": 535510
+				}
+			}
+		}`),
+	// Valid JSON. Missing video object
+	"9": json.RawMessage(`{
+			"id": "adUnit1",
+			"ext": {
+				"appnexus": {
+					"placementId": 12345678,
+					"position": "above",
+					"reserve": 0.35
+				},
+				"rubicon": {
+					"accountId": 23456789,
+					"siteId": 113932,
+					"zoneId": 535510
+				}
+			}
+		}`),
+	// Valid JSON. Missing video object
+	"10": json.RawMessage(`{
+			"ext": {
+				"appnexus": {
+					"placementId": 12345678,
+					"position": "above",
+					"reserve": 0.35
+				}
+			}
+		}`),
+}
+
+// Incoming requests with stored request IDs
+var testStoredRequests = []string{
+	`{
+		"id": "ThisID",
+		"imp": [
+			{
+				"video":{
+					"h":300,
+					"w":200
+				},
+				"ext": {
+					"prebid": {
+						"storedrequest": {
+							"id": "1"
+						},
+						"options": {
+							"echovideoattrs": true
+						}
+					}
+				}
+			}
+		],
+		"ext": {
+			"prebid": {
+				"cache": {
+					"markup": 1
+				},
+				"targeting": {
+				}
+			}
+		}
+	}`,
+	`{
+		"id": "ThisID",
+		"imp": [
+			{
+				"id": "adUnit2",
+				"ext": {
+					"prebid": {
+						"storedrequest": {
+							"id": "1"
+						},
+						"options": {
+							"echovideoattrs": true
+						}
+					},
+					"appnexus": {
+						"placementId": "def",
+						"trafficSourceCode": "mysite.com",
+						"reserve": null
+					},
+					"rubicon": null
+				}
+			}
+		],
+		"ext": {
+			"prebid": {
+				"cache": {
+					"markup": 1
+				},
+				"targeting": {
+				}
+			}
+		}
+	}`,
+	`{
+		"id": "ThisID",
+		"imp": [
+			{
+				"ext": {
+					"prebid": {
+						"storedrequest": {
+							"id": "2"
+						},
+						"options": {
+							"echovideoattrs": false
+						}
+					}
+				}
+			}
+		],
+		"ext": {
+			"prebid": {
+				"storedrequest": {
+					"id": "2"
+				}
+			}
+		}
+	}`,
+	`{
+		"id": "ThisID",
+		"imp": [
+			{
+				"id": "some-static-imp",
+				"video":{
+					"mimes":["video/mp4"]
+				},
+				"ext": {
+					"appnexus": {
+						"placementId": "abc",
+						"position": "below"
+					}
+				}
+			},
+			{
+				"ext": {
+					"prebid": {
+						"storedrequest": {
+							"id": "1"
+						}
+					}
+				}
+			}
+		],
+		"ext": {
+			"prebid": {
+				"cache": {
+					"markup": 1
+				},
+				"targeting": {
+				}
+			}
+		}
+	}`,
+}
+
+// The expected requests after stored request processing
+var testFinalRequests = []string{
+	`{
+		"id": "ThisID",
+		"imp": [
+			{
+				"video":{
+					"h":300,
+					"w":200
+				},
+				"ext":{
+					"appnexus":{
+						"placementId":"abc",
+						"position":"above",
+						"reserve":0.35
+					},
+					"prebid":{
+						"storedrequest":{
+							"id":"1"
+						},
+					"options":{
+						"echovideoattrs":true
+					}
+				},
+				"rubicon":{
+					"accountId":"abc"
+				}
+			},
+			"id":"adUnit1"
+			}
+		],
+		"ext": {
+			"prebid": {
+				"cache": {
+					"markup": 1
+				},
+				"targeting": {
+			}
+		}
+}
+	}`,
+	`{
+		"id": "ThisID",
+		"imp": [
+			{
+				"video":{
+					"w":200,
+					"h":300
+				},
+				"ext":{
+					"appnexus":{
+						"placementId":"def",
+						"position":"above",
+						"trafficSourceCode":"mysite.com"
+					},
+					"prebid":{
+						"storedrequest":{
+							"id":"1"
+						},
+						"options":{
+							"echovideoattrs":true
+						}
+					}
+				},
+				"id":"adUnit2"
+			}
+		],
+		"ext": {
+			"prebid": {
+				"cache": {
+					"markup": 1
+				},
+				"targeting": {
+				}
+			}
+		}
+	}`,
+	`{
+  		"ext": {
+  		  "prebid": {
+  		    "storedrequest": {
+  		      "id": "2"
+  		    },
+  		    "targeting": {
+  		      "pricegranularity": "low"
+  		    }
+  		  }
+  		},
+  		"id": "ThisID",
+  		"imp": [
+  		  {
+  		    "ext": {
+  		      "appnexus": {
+  		        "placementId": "abc",
+  		        "position": "above",
+  		        "reserve": 0.35
+  		      },
+  		      "prebid": {
+  		        "storedrequest": {
+  		          "id": "2"
+  		        },
+  		        "options":{
+					"echovideoattrs":false
+				}
+  		      },
+  		      "rubicon": {
+  		        "accountId": "abc"
+  		      }
+  		    },
+  		    "id": "adUnit1"
+  		  }
+  		],
+  		"tmax": 500
+	}
+`,
+	`{
+	"id": "ThisID",
+	"imp": [
+		{
+    		"id": "some-static-imp",
+    		"video": {
+    		  "mimes": [
+    		    "video/mp4"
+    		  ]
+    		},
+    		"ext": {
+    		  "appnexus": {
+    		    "placementId": "abc",
+    		    "position": "below"
+    		  }
+    		}
+  		},
+  		{
+  		  "ext": {
+  		    "appnexus": {
+  		      "placementId": "abc",
+  		      "position": "above",
+  		      "reserve": 0.35
+  		    },
+  		    "prebid": {
+  		      "storedrequest": {
+  		        "id": "1"
+  		      }
+  		    },
+  		    "rubicon": {
+  		      "accountId": "abc"
+  		    }
+  		  },
+  		  "id": "adUnit1",
+		  "video":{
+				"w":200,
+				"h":300
+          }
+  		}
+	],
+	"ext": {
+		"prebid": {
+			"cache": {
+				"markup": 1
+			},
+			"targeting": {
+			}
+		}
+	}
+}`,
+}
+
+var testStoredImpIds = []string{
+	"adUnit1", "adUnit2", "adUnit1", "some-static-imp",
+}
+
+var testStoredImps = []string{
+	`{
+		"id": "adUnit1",
+        "ext": {
+        	"appnexus": {
+        		"placementId": "abc",
+        		"position": "above",
+        		"reserve": 0.35
+        	},
+        	"rubicon": {
+        		"accountId": "abc"
+        	}
+        },
+		"video":{
+        	"w":200,
+        	"h":300
+		}
+	}`,
+	`{
+		"id": "adUnit1",
+        "ext": {
+        	"appnexus": {
+        		"placementId": "abc",
+        		"position": "above",
+        		"reserve": 0.35
+        	},
+        	"rubicon": {
+        		"accountId": "abc"
+        	}
+        },
+		"video":{
+        	"w":200,
+        	"h":300
+		}
+	}`,
+	`{
+			"id": "adUnit1",
+			"ext": {
+				"appnexus": {
+					"placementId": "abc",
+					"position": "above",
+					"reserve": 0.35
+				},
+				"rubicon": {
+					"accountId": "abc"
+				}
+			}
+		}`,
+	``,
+}
+
+var testBidRequests = []string{
+	`{
+		"id": "ThisID",
+		"app": {
+			"id": "123"
+		},
+		"imp": [
+			{
+				"video":{
+					"h":300,
+					"w":200
+				},
+				"ext": {
+					"prebid": {
+						"storedrequest": {
+							"id": "1"
+						},
+						"options": {
+							"echovideoattrs": true
+						}
+					}
+				}
+			}
+		],
+		"ext": {
+			"prebid": {
+				"cache": {
+					"markup": 1
+				},
+				"targeting": {
+				}
+			}
+		}
+	}`,
+	`{
+		"id": "ThisID",
+		"site": {
+			"page": "prebid.org"
+		},
+		"imp": [
+			{
+				"id": "adUnit2",
+				"ext": {
+					"prebid": {
+						"storedrequest": {
+							"id": "1"
+						},
+						"options": {
+							"echovideoattrs": true
+						}
+					},
+					"appnexus": {
+						"placementId": "def",
+						"trafficSourceCode": "mysite.com",
+						"reserve": null
+					},
+					"rubicon": null
+				}
+			}
+		],
+		"ext": {
+			"prebid": {
+				"storedrequest": {
+					"id": "1"
+				}
+			}
+		}
+	}`,
+	`{
+		"id": "ThisID",
+		"app": {
+			"id": "123"
+		},
+		"imp": [
+			{
+				"ext": {
+					"prebid": {
+						"storedrequest": {
+							"id": "2"
+						},
+						"options": {
+							"echovideoattrs": false
+						}
+					}
+				}
+			}
+		],
+		"ext": {
+			"prebid": {
+				"storedrequest": {
+					"id": "2"
+				}
+			}
+		}
+	}`,
+	`{
+		"id": "ThisID",
+		"site": {
+			"page": "prebid.org"
+		},
+		"imp": [
+			{
+				"ext": {
+					"prebid": {
+						"storedrequest": {
+							"id": "2"
+						},
+						"options": {
+							"echovideoattrs": false
+						}
+					}
+				}
+			}
+		],
+		"ext": {
+			"prebid": {
+				"storedrequest": {
+					"id": "2"
+				}
+			}
+		}
+	}`,
+	`{
+		"id": "ThisID",
+		"app": {
+			"id": "123"
+		},
+		"imp": [
+			{
+				"ext": {
+					"prebid": {
+						"storedrequest": {
+							"id": "1"
+						},
+						"options": {
+							"echovideoattrs": false
+						}
+					}
+				}
+			}
+		],
+		"ext": {
+			"prebid": {
+				"storedrequest": {
+					"id": "1"
+				}
+			}
+		}
+	}`,
+	`{
+		"id": "ThisID",
+		"imp": [{
+			"id": "some-impression-id",
+			"banner": {
+				"format": [{
+						"w": 600,
+						"h": 500
+					},
+					{
+						"w": 300,
+						"h": 600
+					}
+				]
+			},
+			"ext": {
+				"appnexus": {
+					"placementId": 12883451
+				}
+			}
+		}],
+		"ext": {
+			"prebid": {
+				"debug": true,
+				"storedrequest": {
+					"id": "4"
+				}
+			}
+		},
+	  "site": {
+		"page": "https://example.com"
+	  }
+	}`,
+}
+
+// ---------------------------------------------------------
+// Some interfaces implemented with the purspose of testing
+// ---------------------------------------------------------
+
+// mockStoredReqFetcher implements the Fetcher interface
+type mockStoredReqFetcher struct {
+}
+
+func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
+	return testStoredRequestData, testStoredImpData, nil
+}
+
+func (cf mockStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
+	return nil, nil
+}
+
+// mockExchange implements the Exchange interface
+type mockExchange struct {
+	lastRequest *openrtb2.BidRequest
+}
+
+func (m *mockExchange) HoldAuction(ctx context.Context, auctionRequest exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+	r := auctionRequest.BidRequestWrapper
+	m.lastRequest = r.BidRequest
+	return &openrtb2.BidResponse{
+		SeatBid: []openrtb2.SeatBid{{
+			Bid: []openrtb2.Bid{{
+				AdM: "<script></script>",
+			}},
+		}},
+	}, nil
+}
+
+// hardcodedResponseIPValidator implements the IPValidator interface.
+type hardcodedResponseIPValidator struct {
+	response bool
+}
+
+func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool {
+	return v.response
+}
+
+// fakeUUIDGenerator implements the UUIDGenerator interface
+type fakeUUIDGenerator struct {
+	id  string
+	err error
+}
+
+func (f fakeUUIDGenerator) Generate() (string, error) {
+	return f.id, f.err
+}
+
+// warningsCheckExchange is a well-behaved exchange which stores all incoming warnings.
+// implements the Exchange interface
+type warningsCheckExchange struct {
+	auctionRequest exchange.AuctionRequest
+}
+
+func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+	e.auctionRequest = r
+	return nil, nil
+}
+
+// nobidExchange is a well-behaved exchange which always bids "no bid".
+// implements the Exchange interface
+type nobidExchange struct {
+	gotRequest *openrtb2.BidRequest
+}
+
+func (e *nobidExchange) HoldAuction(ctx context.Context, auctionRequest exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) {
+	r := auctionRequest.BidRequestWrapper
+	e.gotRequest = r.BidRequest
+	return &openrtb2.BidResponse{
+		ID:    r.BidRequest.ID,
+		BidID: "test bid id",
+		NBR:   openrtb2.NoBidReasonCodeUnknownError.Ptr(),
+	}, nil
+}
+
+// mockCurrencyRatesClient is a mock currency rate server and the rates it returns
+// are set in the JSON test file
+type mockCurrencyRatesClient struct {
+	data currencyInfo
+}
+
+type currencyInfo struct {
+	Conversions map[string]map[string]float64 `json:"conversions"`
+	DataAsOfRaw string                        `json:"dataAsOf"`
+}
+
+func (s mockCurrencyRatesClient) handle(w http.ResponseWriter, req *http.Request) {
+	s.data.DataAsOfRaw = "2018-09-12"
+
+	// Marshal the response and http write it
+	currencyServerJsonResponse, err := json.Marshal(&s.data)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	w.Write(currencyServerJsonResponse)
+	return
+}
+
+// mockBidderHandler carries mock bidder server information that will be read from JSON test files
+// and defines a handle function of a a mock bidder service.
+type mockBidderHandler struct {
+	BidderName string  `json:"bidderName"`
+	Currency   string  `json:"currency"`
+	Price      float64 `json:"price"`
+}
+
+func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) {
+	// Read request Body
+	buf := new(bytes.Buffer)
+	buf.ReadFrom(req.Body)
+
+	// Unmarshal exit if error
+	var openrtb2Request openrtb2.BidRequest
+	if err := json.Unmarshal(buf.Bytes(), &openrtb2Request); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	var openrtb2ImpExt map[string]json.RawMessage
+	if err := json.Unmarshal(openrtb2Request.Imp[0].Ext, &openrtb2ImpExt); err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	_, exists := openrtb2ImpExt["bidder"]
+	if !exists {
+		http.Error(w, "This request is not meant for this bidder", http.StatusBadRequest)
+		return
+	}
+
+	// Create bid service openrtb2.BidResponse with one bid according to JSON test file values
+	var serverResponseObject = openrtb2.BidResponse{
+		ID:  openrtb2Request.ID,
+		Cur: b.Currency,
+		SeatBid: []openrtb2.SeatBid{
+			{
+				Bid: []openrtb2.Bid{
+					{
+						ID:    b.BidderName + "-bid",
+						ImpID: openrtb2Request.Imp[0].ID,
+						Price: b.Price,
+					},
+				},
+				Seat: b.BidderName,
+			},
+		},
+	}
+
+	// Marshal the response and http write it
+	serverJsonResponse, err := json.Marshal(&serverResponseObject)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	w.Write(serverJsonResponse)
+	return
+}
+
+// mockAdapter is a mock impression-splitting adapter
+type mockAdapter struct {
+	mockServerURL string
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
+	adapter := &mockAdapter{
+		mockServerURL: config.Endpoint,
+	}
+	return adapter, nil
+}
+
+func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var requests []*adapters.RequestData
+	var errors []error
+
+	requestCopy := *request
+	for _, imp := range request.Imp {
+		requestCopy.Imp = []openrtb2.Imp{imp}
+
+		requestJSON, err := json.Marshal(request)
+		if err != nil {
+			errors = append(errors, err)
+			continue
+		}
+
+		requestData := &adapters.RequestData{
+			Method: "POST",
+			Uri:    a.mockServerURL,
+			Body:   requestJSON,
+		}
+		requests = append(requests, requestData)
+	}
+	return requests, errors
+}
+
+func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if responseData.StatusCode != http.StatusOK {
+		switch responseData.StatusCode {
+		case http.StatusNoContent:
+			return nil, nil
+		case http.StatusBadRequest:
+			return nil, []error{&errortypes.BadInput{
+				Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
+			}}
+		default:
+			return nil, []error{&errortypes.BadServerResponse{
+				Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
+			}}
+		}
+	}
+
+	var publisherResponse openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &publisherResponse); err != nil {
+		return nil, []error{err}
+	}
+
+	rv := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	rv.Currency = publisherResponse.Cur
+	for _, seatBid := range publisherResponse.SeatBid {
+		for i, bid := range seatBid.Bid {
+			for _, imp := range request.Imp {
+				if imp.ID == bid.ImpID {
+					b := &adapters.TypedBid{
+						Bid:     &seatBid.Bid[i],
+						BidType: openrtb_ext.BidTypeBanner,
+					}
+					rv.Bids = append(rv.Bids, b)
+				}
+			}
+		}
+	}
+	return rv, nil
+}
+
+// ---------------------------------------------------------
+// Auxiliary functions that don't make assertions and don't
+// take t *testing.T as parameter
+// ---------------------------------------------------------
+func getBidderInfos(cfg map[string]config.Adapter, biddersNames []openrtb_ext.BidderName) config.BidderInfos {
+	biddersInfos := make(config.BidderInfos)
+	for _, name := range biddersNames {
+		adapterConfig, ok := cfg[string(name)]
+		if !ok {
+			adapterConfig = config.Adapter{}
+		}
+		biddersInfos[string(name)] = newBidderInfo(adapterConfig)
+	}
+	return biddersInfos
+}
+
+func newBidderInfo(cfg config.Adapter) config.BidderInfo {
+	return config.BidderInfo{
+		Enabled: !cfg.Disabled,
+	}
+}
+
+func getTestFiles(dir string) ([]string, error) {
+	var filesToAssert []string
+
+	fileList, err := ioutil.ReadDir(dir)
+	if err != nil {
+		return nil, err
+	}
+
+	// Append the path of every file found in `dir` to the `filesToAssert` array
+	for _, fileInfo := range fileList {
+		filesToAssert = append(filesToAssert, filepath.Join(dir, fileInfo.Name()))
+	}
+
+	return filesToAssert, nil
+}
+
+func parseTestFile(fileData []byte, testFile string) (testCase, error) {
+
+	parsedTestData := testCase{}
+	var err, errEm error
+
+	// Get testCase values
+	parsedTestData.BidRequest, _, _, err = jsonparser.Get(fileData, "mockBidRequest")
+	if err != nil {
+		return parsedTestData, fmt.Errorf("Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", testFile, err)
+	}
+
+	// Get testCaseConfig values
+	parsedTestData.Config = &testConfigValues{}
+	var jsonTestConfig json.RawMessage
+
+	jsonTestConfig, _, _, err = jsonparser.Get(fileData, "config")
+	if err == nil {
+		if err = json.Unmarshal(jsonTestConfig, parsedTestData.Config); err != nil {
+			return parsedTestData, fmt.Errorf("Error unmarshaling root.config from file %s. Desc: %v.", testFile, err)
+		}
+	}
+
+	// Get the return code we expect PBS to throw back given test's bidRequest and config
+	parsedReturnCode, err := jsonparser.GetInt(fileData, "expectedReturnCode")
+	if err != nil {
+		return parsedTestData, fmt.Errorf("Error jsonparsing root.code from file %s. Desc: %v.", testFile, err)
+	}
+
+	// Get both bid response and error message, if any
+	parsedTestData.ExpectedBidResponse, _, _, err = jsonparser.Get(fileData, "expectedBidResponse")
+	parsedTestData.ExpectedErrorMessage, errEm = jsonparser.GetString(fileData, "expectedErrorMessage")
+
+	if err == nil && errEm == nil {
+		return parsedTestData, errors.New("Test case file can't have both a valid expectedBidResponse and a valid expectedErrorMessage, fields are mutually exclusive")
+	} else if err != nil && errEm != nil {
+		return parsedTestData, errors.New("Test case file should come with either a valid expectedBidResponse or a valid expectedErrorMessage, not both.")
+	}
+
+	parsedTestData.ExpectedReturnCode = int(parsedReturnCode)
+
+	return parsedTestData, nil
+}
+
+func (tc *testConfigValues) getBlacklistedAppMap() map[string]bool {
+	var blacklistedAppMap map[string]bool
+
+	if len(tc.BlacklistedApps) > 0 {
+		blacklistedAppMap = make(map[string]bool, len(tc.BlacklistedApps))
+		for _, app := range tc.BlacklistedApps {
+			blacklistedAppMap[app] = true
+		}
+	}
+	return blacklistedAppMap
+}
+
+func (tc *testConfigValues) getBlackListedAccountMap() map[string]bool {
+	var blacklistedAccountMap map[string]bool
+
+	if len(tc.BlacklistedAccounts) > 0 {
+		blacklistedAccountMap = make(map[string]bool, len(tc.BlacklistedAccounts))
+		for _, account := range tc.BlacklistedAccounts {
+			blacklistedAccountMap[account] = true
+		}
+	}
+	return blacklistedAccountMap
+}
+
+func (tc *testConfigValues) getAdaptersConfigMap() map[string]config.Adapter {
+	var adaptersConfig map[string]config.Adapter
+
+	if len(tc.DisabledAdapters) > 0 {
+		adaptersConfig = make(map[string]config.Adapter, len(tc.DisabledAdapters))
+		for _, adapterName := range tc.DisabledAdapters {
+			adaptersConfig[adapterName] = config.Adapter{Disabled: true}
+		}
+	}
+	return adaptersConfig
+}
+
+// buildTestExchange returns an exchange with mock bidder servers and mock currency convertion server
+func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.BidderName]exchange.AdaptedBidder, mockBidServersArray []*httptest.Server, mockCurrencyRatesServer *httptest.Server, bidderInfos config.BidderInfos, cfg *config.Configuration, met metrics.MetricsEngine, mockFetcher stored_requests.CategoryFetcher) (exchange.Exchange, []*httptest.Server) {
+	if len(testCfg.MockBidders) == 0 {
+		testCfg.MockBidders = append(testCfg.MockBidders, mockBidderHandler{BidderName: "appnexus", Currency: "USD", Price: 0.00})
+	}
+	for _, mockBidder := range testCfg.MockBidders {
+		bidServer := httptest.NewServer(http.HandlerFunc(mockBidder.bid))
+		bidderAdapter := mockAdapter{mockServerURL: bidServer.URL}
+		bidderName := openrtb_ext.BidderName(mockBidder.BidderName)
+
+		adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil)
+		mockBidServersArray = append(mockBidServersArray, bidServer)
+	}
+
+	mockCurrencyConverter := currency.NewRateConverter(mockCurrencyRatesServer.Client(), mockCurrencyRatesServer.URL, time.Second)
+	mockCurrencyConverter.Run()
+
+	return exchange.NewExchange(adapterMap,
+		&wellBehavedCache{},
+		cfg,
+		nil,
+		met,
+		bidderInfos,
+		gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker),
+		mockCurrencyConverter,
+		mockFetcher,
+	), mockBidServersArray
+}
+
+// buildTestEndpoint instantiates an openrtb2 Auction endpoint designed to test endpoints/openrtb2/auction.go
+func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Handle, []*httptest.Server, *httptest.Server, error) {
+	if test.Config == nil {
+		test.Config = &testConfigValues{}
+	}
+
+	var paramValidator openrtb_ext.BidderParamValidator
+	if test.Config.RealParamsValidator {
+		var err error
+		paramValidator, err = openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+		if err != nil {
+			return nil, nil, nil, err
+		}
+	} else {
+		paramValidator = mockBidderParamValidator{}
+	}
+
+	bidderInfos := getBidderInfos(test.Config.getAdaptersConfigMap(), openrtb_ext.CoreBidderNames())
+	bidderMap := exchange.GetActiveBidders(bidderInfos)
+	disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos)
+	met := &metricsConfig.NilMetricsEngine{}
+	mockFetcher := empty_fetcher.EmptyFetcher{}
+
+	// Adapter map with mock adapters needed to run JSON test cases
+	adapterMap := make(map[openrtb_ext.BidderName]exchange.AdaptedBidder, 0)
+	mockBidServersArray := make([]*httptest.Server, 0, 3)
+
+	// Mock prebid Server's currency converter, instantiate and start
+	mockCurrencyConversionService := mockCurrencyRatesClient{
+		currencyInfo{
+			Conversions: test.Config.CurrencyRates,
+		},
+	}
+	mockCurrencyRatesServer := httptest.NewServer(http.HandlerFunc(mockCurrencyConversionService.handle))
+
+	ex, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher)
+
+	var storedRequestFetcher stored_requests.Fetcher
+	if len(test.storedRequest) > 0 {
+		storedRequestFetcher = &mockAmpStoredReqFetcher{test.storedRequest}
+	} else {
+		storedRequestFetcher = &mockStoredReqFetcher{}
+	}
+
+	var storedResponseFetcher stored_requests.Fetcher
+	if len(test.StoredResponse) > 0 {
+		storedResponseFetcher = &mockAmpStoredResponseFetcher{test.StoredResponse}
+	} else {
+		storedResponseFetcher = empty_fetcher.EmptyFetcher{}
+	}
+
+	var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.PBSAnalyticsModule, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher) (httprouter.Handle, error)
+
+	switch test.endpointType {
+	case AMP_ENDPOINT:
+		endpointBuilder = NewAmpEndpoint
+	default: //case OPENRTB_ENDPOINT:
+		endpointBuilder = NewEndpoint
+	}
+
+	endpoint, err := endpointBuilder(
+		fakeUUIDGenerator{},
+		ex,
+		paramValidator,
+		storedRequestFetcher,
+		mockFetcher,
+		cfg,
+		met,
+		analyticsConf.NewPBSAnalytics(&config.Analytics{}),
+		disabledBidders,
+		[]byte(test.Config.AliasJSON),
+		bidderMap,
+		storedResponseFetcher,
+	)
+
+	return endpoint, mockBidServersArray, mockCurrencyRatesServer, err
+}
+
+type mockBidderParamValidator struct{}
+
+func (v mockBidderParamValidator) Validate(name openrtb_ext.BidderName, ext json.RawMessage) error {
+	return nil
+}
+func (v mockBidderParamValidator) Schema(name openrtb_ext.BidderName) string { return "" }
+
+type mockAmpStoredReqFetcher struct {
+	data map[string]json.RawMessage
+}
+
+func (cf *mockAmpStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
+	return cf.data, nil, nil
+}
+
+func (cf *mockAmpStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
+	return nil, nil
+}
+
+type mockAmpStoredResponseFetcher struct {
+	data map[string]json.RawMessage
+}
+
+func (cf *mockAmpStoredResponseFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
+	return nil, nil, nil
+}
+
+func (cf *mockAmpStoredResponseFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
+	for _, storedResponseID := range ids {
+		if storedResponse, exists := cf.data[storedResponseID]; exists {
+			// Found. Unescape string before returning
+			response, err := strconv.Unquote(string(storedResponse))
+			if err != nil {
+				return nil, append([]error{}, err)
+			}
+			cf.data[storedResponseID] = json.RawMessage(response)
+			return cf.data, nil
+		}
+	}
+	return nil, nil
+}
+
+type wellBehavedCache struct{}
+
+func (c *wellBehavedCache) GetExtCacheData() (scheme string, host string, path string) {
+	return "https", "www.pbcserver.com", "/pbcache/endpoint"
+}
+
+func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) ([]string, []error) {
+	ids := make([]string, len(values))
+	for i := 0; i < len(values); i++ {
+		ids[i] = strconv.Itoa(i)
+	}
+	return ids, nil
+}
+
+func readFile(t *testing.T, filename string) []byte {
+	data, err := ioutil.ReadFile(filename)
+	if err != nil {
+		t.Fatalf("Failed to read file %s: %v", filename, err)
+	}
+	return data
+}
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 0969bca7..8ffa945a 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -1180,7 +1180,7 @@ func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *m
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		ex,
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1224,7 +1224,7 @@ func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps {
 	return &endpointDeps{
 		fakeUUIDGenerator{},
 		ex,
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1246,7 +1246,7 @@ func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames)
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		ex,
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1270,7 +1270,7 @@ func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps {
 	edep := &endpointDeps{
 		fakeUUIDGenerator{},
 		ex,
-		newParamsValidator(t),
+		mockBidderParamValidator{},
 		&mockVideoStoredReqFetcher{},
 		&mockVideoStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index 9ce1e57b..2423b019 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -26,32 +26,32 @@ func TestBuildAdapters(t *testing.T) {
 
 	appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{})
 	appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled)
-	appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil)
+	appnexusBidderAdapted := AdaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil)
 	appnexusValidated := addValidatedBidderMiddleware(appnexusBidderAdapted)
 
 	rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{})
 	rubiconBidderWithInfo := adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled)
-	rubiconBidderAdapted := adaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil)
+	rubiconBidderAdapted := AdaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil)
 	rubiconbidderValidated := addValidatedBidderMiddleware(rubiconBidderAdapted)
 
 	testCases := []struct {
 		description     string
 		adapterConfig   map[string]config.Adapter
 		bidderInfos     map[string]config.BidderInfo
-		expectedBidders map[openrtb_ext.BidderName]adaptedBidder
+		expectedBidders map[openrtb_ext.BidderName]AdaptedBidder
 		expectedErrors  []error
 	}{
 		{
 			description:     "No Bidders",
 			adapterConfig:   map[string]config.Adapter{},
 			bidderInfos:     map[string]config.BidderInfo{},
-			expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{},
+			expectedBidders: map[openrtb_ext.BidderName]AdaptedBidder{},
 		},
 		{
 			description:   "One Bidder",
 			adapterConfig: map[string]config.Adapter{"appnexus": {}},
 			bidderInfos:   map[string]config.BidderInfo{"appnexus": infoEnabled},
-			expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{
+			expectedBidders: map[openrtb_ext.BidderName]AdaptedBidder{
 				openrtb_ext.BidderAppnexus: appnexusValidated,
 			},
 		},
@@ -59,7 +59,7 @@ func TestBuildAdapters(t *testing.T) {
 			description:   "Many Bidders",
 			adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}},
 			bidderInfos:   map[string]config.BidderInfo{"appnexus": infoEnabled, "rubicon": infoEnabled},
-			expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{
+			expectedBidders: map[openrtb_ext.BidderName]AdaptedBidder{
 				openrtb_ext.BidderAppnexus: appnexusValidated,
 				openrtb_ext.BidderRubicon:  rubiconbidderValidated,
 			},
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 3b9879dc..719af304 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -91,7 +91,7 @@ func TestSingleBidder(t *testing.T) {
 		}
 		bidderImpl.bidResponse = mockBidderResponse
 
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo)
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		bidderReq := BidderRequest{
@@ -177,7 +177,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
 	debugInfo := &config.DebugInfo{Allow: true}
 	ctx := context.Background()
 
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -222,7 +222,7 @@ func TestSetGPCHeader(t *testing.T) {
 	debugInfo := &config.DebugInfo{Allow: true}
 	ctx := context.Background()
 
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -263,7 +263,7 @@ func TestSetGPCHeaderNil(t *testing.T) {
 	debugInfo := &config.DebugInfo{Allow: true}
 	ctx := context.Background()
 
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
@@ -326,7 +326,7 @@ func TestMultiBidder(t *testing.T) {
 			}},
 		bidResponse: mockBidderResponse,
 	}
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -685,7 +685,7 @@ func TestMultiCurrencies(t *testing.T) {
 		)
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -842,7 +842,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 		bidderReq := BidderRequest{
 			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -1007,7 +1007,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
 		}
 
 		// Execute:
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(
 			&http.Client{},
 			mockedHTTPServer.URL,
@@ -1318,7 +1318,7 @@ func TestMobileNativeTypes(t *testing.T) {
 			},
 			bidResponse: tc.mockBidderResponse,
 		}
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		bidderReq := BidderRequest{
@@ -1388,7 +1388,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 	for _, tc := range testCases {
 
 		bidderImpl := &goodSingleBidderWithStoredBidResp{}
-		bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 		bidderReq := BidderRequest{
@@ -1416,7 +1416,7 @@ func TestRequestBidsStoredBidResponses(t *testing.T) {
 }
 
 func TestErrorReporting(t *testing.T) {
-	bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
+	bidder := AdaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	bidderReq := BidderRequest{
 		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
@@ -1641,7 +1641,7 @@ func TestCallRecordAdapterConnections(t *testing.T) {
 	metrics.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once()
 
 	// Run requestBid using an http.Client with a mock handler
-	bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil)
+	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil)
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	bidderReq := BidderRequest{
diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go
index 250d2599..364726c2 100644
--- a/exchange/bidder_validate_bids_test.go
+++ b/exchange/bidder_validate_bids_test.go
@@ -12,7 +12,7 @@ import (
 )
 
 func TestAllValidBids(t *testing.T) {
-	var bidder adaptedBidder = addValidatedBidderMiddleware(&mockAdaptedBidder{
+	var bidder AdaptedBidder = addValidatedBidderMiddleware(&mockAdaptedBidder{
 		bidResponse: &pbsOrtbSeatBid{
 			bids: []*pbsOrtbBid{
 				{
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index d7a47121..e06ec9d2 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -320,8 +320,8 @@ func TestDebugBehaviour(t *testing.T) {
 	// Run tests
 	for _, test := range testCases {
 
-		e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
-			openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}),
+		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
+			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}),
 		}
 
 		bidRequest.Test = test.in.test
@@ -501,9 +501,9 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
 			TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 		}
 
-		e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
-			openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}),
-			openrtb_ext.BidderTelaria:  adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}),
+		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
+			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}),
+			openrtb_ext.BidderTelaria:  AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}),
 		}
 		// Run test
 		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog)
@@ -656,8 +656,8 @@ func TestOverrideWithCustomCurrency(t *testing.T) {
 			Currency: "USD",
 		}
 
-		e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
-			openrtb_ext.BidderAppnexus: adaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
+			openrtb_ext.BidderAppnexus: AdaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
 		}
 
 		// Set custom rates in extension
@@ -730,8 +730,8 @@ func TestAdapterCurrency(t *testing.T) {
 		currencyConverter: currencyConverter,
 		categoriesFetcher: nilCategoryFetcher{},
 		bidIDGenerator:    &mockBidIDGenerator{false, false},
-		adapterMap: map[openrtb_ext.BidderName]adaptedBidder{
-			openrtb_ext.BidderName("foo"): adaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil),
+		adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{
+			openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil),
 		},
 	}
 
@@ -1113,8 +1113,8 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 	}
 
 	e := new(exchange)
-	e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
-		openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
+		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
@@ -1406,8 +1406,8 @@ func TestBidReturnsCreative(t *testing.T) {
 		bidResponse: &adapters.BidderResponse{},
 	}
 	e := new(exchange)
-	e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
-		openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
+	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
+		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil),
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
@@ -2225,7 +2225,7 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidRespons
 }
 
 func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator) Exchange {
-	bidderAdapters := make(map[openrtb_ext.BidderName]adaptedBidder, len(expectations))
+	bidderAdapters := make(map[openrtb_ext.BidderName]AdaptedBidder, len(expectations))
 	bidderInfos := make(config.BidderInfos, len(expectations))
 	for _, bidderName := range openrtb_ext.CoreBidderNames() {
 		if spec, ok := expectations[string(bidderName)]; ok {
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index f164ae01..18cc6fc4 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -128,10 +128,10 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 	return buildBidMap(bidResp.SeatBid, len(mockBids))
 }
 
-func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]adaptedBidder {
-	adapterMap := make(map[openrtb_ext.BidderName]adaptedBidder, len(bids))
+func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]AdaptedBidder {
+	adapterMap := make(map[openrtb_ext.BidderName]AdaptedBidder, len(bids))
 	for bidder, bids := range bids {
-		adapterMap[bidder] = adaptBidder(&mockTargetingBidder{
+		adapterMap[bidder] = AdaptBidder(&mockTargetingBidder{
 			mockServerURL: mockServerURL,
 			bids:          bids,
 		}, client, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil)