Repository: prebid/prebid-server
Pull request #2547 (2023-01-26 15:11:46)

Issue tracker fix
Update the issue tracker action to reflect changes in the GH API. See also https://github.com/prebid/Prebid.js/pull/9466/


Patch:
diff --git a/.github/workflows/cross-repo-issue.yml b/.github/workflows/cross-repo-issue.yml
new file mode 100644
index 00000000..1c9c0a7e
--- /dev/null
+++ b/.github/workflows/cross-repo-issue.yml
@@ -0,0 +1,28 @@
+name: Cross-repo Issue Creation
+
+on:
+  pull_request:
+    types: [closed]
+    branches:
+      - "master"
+
+jobs:
+  cross-repo:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Generate token
+        id: generate_token
+        uses: tibdex/github-app-token@v1
+        with:
+          app_id: ${{ secrets.XREPO_APP_ID }}
+          private_key: ${{ secrets.XREPO_PEM }}
+      - name: create issue in other repo
+        if: "!contains(github.event.pull_request.labels.*.name, 'do not port') && github.event.pull_request.merged"
+        env:
+          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
+        run: |
+          echo -e "A PR was merged over on PBS-Go\n\n- [https://github.com/prebid/prebid-server/pull/${{github.event.number}}](https://github.com/prebid/prebid-server/pull/${{github.event.number}})\n- timestamp: ${{ github.event.pull_request.merged_at}}" > msg
+          export msg=$(cat msg)
+          gh issue create --repo prebid/prebid-server-java --title "Port PR from PBS-Go: ${{ github.event.pull_request.title }}" \
+              --body "$msg" \
+              --label auto
diff --git a/.github/workflows/issue_prioritization.yml b/.github/workflows/issue_prioritization.yml
index 9a178577..3843507b 100644
--- a/.github/workflows/issue_prioritization.yml
+++ b/.github/workflows/issue_prioritization.yml
@@ -25,21 +25,30 @@ jobs:
           gh api graphql -f query='
             query($org: String!, $number: Int!) {
               organization(login: $org){
-                projectNext(number: $number) {
+                projectV2(number: $number) {
                   id
                   fields(first:100) {
                     nodes {
-                      id
-                      name
-                      settings
+                      ... on ProjectV2Field {
+                        id
+                        name
+                      }
+                      ... on ProjectV2SingleSelectField {
+                        id
+                        name
+                        options {
+                          id
+                          name
+                        }
+                      }
                     }
                   }
                 }
               }
             }' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
 
-          echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
-          echo 'DATE_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV
+          echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
+          echo 'DATE_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name=="'"$DATE_FIELD"'") | .id' project_data.json) >> $GITHUB_ENV
 
       - name: Add issue to project
         env:
@@ -48,9 +57,9 @@ jobs:
         run: |
           gh api graphql -f query='
             mutation($project:ID!, $issue:ID!) {
-              addProjectNextItem(input: {projectId: $project, contentId: $issue}) {
-                projectNextItem {
-                  id,
+              addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
+                item {
+                  id
                   content {
                      ... on Issue {
                         createdAt
@@ -63,8 +72,8 @@ jobs:
               }
             }' -f project=$PROJECT_ID -f issue=$ISSUE_ID > issue_data.json
           
-          echo 'ITEM_ID='$(jq '.data.addProjectNextItem.projectNextItem.id' issue_data.json) >> $GITHUB_ENV
-          echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectNextItem.projectNextItem.content.createdAt' issue_data.json) >> $GITHUB_ENV
+          echo 'ITEM_ID='$(jq '.data.addProjectV2ItemById.item.id' issue_data.json) >> $GITHUB_ENV
+          echo 'ITEM_CREATION_DATE='$(jq '.data.addProjectV2ItemById.item.content.createdAt' issue_data.json | cut -c 2-11) >> $GITHUB_ENV
 
       - name: Set fields
         env:
@@ -75,15 +84,17 @@ jobs:
               $project: ID!
               $item: ID!
               $date_field: ID!
-              $date_value: String!
+              $date_value: Date!
             ) {
-              set_creation_date: updateProjectNextItemField(input: {
+              set_creation_date: updateProjectV2ItemFieldValue(input: {
                 projectId: $project
                 itemId: $item
                 fieldId: $date_field
-                value: $date_value
+                value: {
+                  date: $date_value
+                }
               }) {
-                projectNextItem {
+                projectV2Item {
                   id
                 }
               }
diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go
index 3b01dab5..084ffc22 100644
--- a/adapters/adnuntius/adnuntius.go
+++ b/adapters/adnuntius/adnuntius.go
@@ -76,8 +76,9 @@ const minutesInHour = 60
 // Builder builds a new instance of the Adnuntius adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	bidder := &adapter{
-		time:     &timeutil.RealTime{},
-		endpoint: config.Endpoint,
+		time:      &timeutil.RealTime{},
+		endpoint:  config.Endpoint,
+		extraInfo: config.ExtraAdapterInfo,
 	}
 
 	return bidder, nil
diff --git a/adapters/brave/brave.go b/adapters/brave/brave.go
new file mode 100755
index 00000000..627b6547
--- /dev/null
+++ b/adapters/brave/brave.go
@@ -0,0 +1,157 @@
+package brave
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/prebid/openrtb/v17/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/errortypes"
+	"github.com/prebid/prebid-server/macros"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint *template.Template
+}
+
+// Builder builds a new instance of the Brave adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	uri, err := template.New("endpointTemplate").Parse(config.Endpoint)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
+	}
+
+	bidder := &adapter{
+		endpoint: uri,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var braveExt *openrtb_ext.ExtImpBrave
+	var err error
+
+	braveExt, err = a.getImpressionExt(&request.Imp[0])
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	request.Imp[0].Ext = nil
+
+	reqJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	url, err := a.buildEndpointURL(braveExt)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	return []*adapters.RequestData{{
+		Method:  http.MethodPost,
+		Body:    reqJSON,
+		Uri:     url,
+		Headers: headers,
+	}}, nil
+}
+
+func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpBrave, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "ext.bidder not provided",
+		}
+	}
+	var braveExt openrtb_ext.ExtImpBrave
+	if err := json.Unmarshal(bidderExt.Bidder, &braveExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "ext.bidder not provided",
+		}
+	}
+	return &braveExt, nil
+}
+
+func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtImpBrave) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{PublisherID: params.PlacementID}
+	return macros.ResolveMacros(a.endpoint, endpointParams)
+}
+
+func (a *adapter) MakeBids(
+	receivedRequest *openrtb2.BidRequest,
+	bidderRequest *adapters.RequestData,
+	bidderResponse *adapters.ResponseData,
+) (
+	*adapters.BidderResponse,
+	[]error,
+) {
+
+	if bidderResponse.StatusCode == http.StatusNoContent {
+		return nil, []error{&errortypes.BadInput{Message: "No bid"}}
+	}
+
+	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", bidderResponse.StatusCode),
+		}}
+	}
+
+	if bidderResponse.StatusCode == http.StatusServiceUnavailable {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Service Unavailable. Status Code: [ %d ] ", bidderResponse.StatusCode),
+		}}
+	}
+
+	if bidderResponse.StatusCode != http.StatusOK {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", bidderResponse.StatusCode),
+		}}
+	}
+
+	var bidResponse openrtb2.BidResponse
+	if err := json.Unmarshal(bidderResponse.Body, &bidResponse); err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Bad Server Response",
+		}}
+	}
+
+	if len(bidResponse.SeatBid) == 0 {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: "Empty SeatBid array",
+		}}
+	}
+
+	bidResponseFinal := adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.SeatBid[0].Bid))
+	sb := bidResponse.SeatBid[0]
+
+	for _, bid := range sb.Bid {
+		bidResponseFinal.Bids = append(bidResponseFinal.Bids, &adapters.TypedBid{
+			Bid:     &bid,
+			BidType: getMediaTypeForImp(bid.ImpID, receivedRequest.Imp),
+		})
+	}
+	return bidResponseFinal, nil
+}
+
+func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
+	mediaType := openrtb_ext.BidTypeBanner
+	for _, imp := range imps {
+		if imp.ID == impId {
+			if imp.Video != nil {
+				mediaType = openrtb_ext.BidTypeVideo
+			} else if imp.Native != nil {
+				mediaType = openrtb_ext.BidTypeNative
+			}
+			return mediaType
+		}
+	}
+	return mediaType
+}
diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go
index 2240bd76..269d1c77 100644
--- a/adapters/openx/openx.go
+++ b/adapters/openx/openx.go
@@ -15,11 +15,13 @@ import (
 const hbconfig = "hb_pbs_1.0.0"
 
 type OpenxAdapter struct {
-	endpoint string
+	bidderName string
+	endpoint   string
 }
 
 type openxImpExt struct {
-	CustomParams map[string]interface{} `json:"customParams,omitempty"`
+	CustomParams       map[string]interface{}             `json:"customParams,omitempty"`
+	AuctionEnvironment openrtb_ext.AuctionEnvironmentType `json:"ae,omitempty"`
 }
 
 type openxReqExt struct {
@@ -28,6 +30,10 @@ type openxReqExt struct {
 	BidderConfig string `json:"bc"`
 }
 
+type openxRespExt struct {
+	FledgeAuctionConfigs map[string]json.RawMessage `json:"fledge_auction_configs,omitempty"`
+}
+
 func (a *OpenxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 	var errs []error
 	var bannerImps []openrtb2.Imp
@@ -133,18 +139,27 @@ func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error {
 	if imp.BidFloor == 0 && openxExt.CustomFloor > 0 {
 		imp.BidFloor = openxExt.CustomFloor
 	}
-	imp.Ext = nil
+
+	impExt := openxImpExt{}
+	addImpExt := false
 
 	if openxExt.CustomParams != nil {
-		impExt := openxImpExt{
-			CustomParams: openxExt.CustomParams,
-		}
+		impExt.CustomParams = openxExt.CustomParams
+		addImpExt = true
+	}
+	if bidderExt.AuctionEnvironment != openrtb_ext.ServerSideAuction {
+		impExt.AuctionEnvironment = bidderExt.AuctionEnvironment
+		addImpExt = true
+	}
+	if addImpExt {
 		var err error
 		if imp.Ext, err = json.Marshal(impExt); err != nil {
 			return &errortypes.BadInput{
 				Message: err.Error(),
 			}
 		}
+	} else {
+		imp.Ext = nil
 	}
 
 	if imp.Video != nil {
@@ -189,6 +204,21 @@ func (a *OpenxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRe
 		bidResponse.Currency = bidResp.Cur
 	}
 
+	if bidResp.Ext != nil {
+		var bidRespExt openxRespExt
+		if err := json.Unmarshal(bidResp.Ext, &bidRespExt); err == nil && bidRespExt.FledgeAuctionConfigs != nil {
+			bidResponse.FledgeAuctionConfigs = make([]*openrtb_ext.FledgeAuctionConfig, 0, len(bidRespExt.FledgeAuctionConfigs))
+			for impId, config := range bidRespExt.FledgeAuctionConfigs {
+				fledgeAuctionConfig := &openrtb_ext.FledgeAuctionConfig{
+					ImpId:  impId,
+					Bidder: a.bidderName,
+					Config: config,
+				}
+				bidResponse.FledgeAuctionConfigs = append(bidResponse.FledgeAuctionConfigs, fledgeAuctionConfig)
+			}
+		}
+	}
+
 	for _, sb := range bidResp.SeatBid {
 		for i := range sb.Bid {
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
@@ -220,7 +250,8 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
 // Builder builds a new instance of the Openx adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	bidder := &OpenxAdapter{
-		endpoint: config.Endpoint,
+		endpoint:   config.Endpoint,
+		bidderName: string(bidderName),
 	}
 	return bidder, nil
 }
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index f808a404..5378e7a5 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -51,6 +51,7 @@ import (
 	"github.com/prebid/prebid-server/adapters/blue"
 	"github.com/prebid/prebid-server/adapters/bmtm"
 	"github.com/prebid/prebid-server/adapters/boldwin"
+	"github.com/prebid/prebid-server/adapters/brave"
 	"github.com/prebid/prebid-server/adapters/brightroll"
 	"github.com/prebid/prebid-server/adapters/ccx"
 	"github.com/prebid/prebid-server/adapters/coinzilla"
@@ -215,6 +216,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderBlue:              blue.Builder,
 		openrtb_ext.BidderBmtm:              bmtm.Builder,
 		openrtb_ext.BidderBoldwin:           boldwin.Builder,
+		openrtb_ext.BidderBrave:             brave.Builder,
 		openrtb_ext.BidderBrightroll:        brightroll.Builder,
 		openrtb_ext.BidderCcx:               ccx.Builder,
 		openrtb_ext.BidderCoinzilla:         coinzilla.Builder,
diff --git a/exchange/price_granularity.go b/exchange/price_granularity.go
index 242d420f..af3300b2 100644
--- a/exchange/price_granularity.go
+++ b/exchange/price_granularity.go
@@ -13,7 +13,7 @@ func GetPriceBucket(cpm float64, config openrtb_ext.PriceGranularity) string {
 	bucketMax := 0.0
 	increment := 0.0
 	precision := config.Precision
-
+	bucketMin := 0.0
 	for i := 0; i < len(config.Ranges); i++ {
 		if config.Ranges[i].Max > bucketMax {
 			bucketMax = config.Ranges[i].Max
@@ -21,6 +21,7 @@ func GetPriceBucket(cpm float64, config openrtb_ext.PriceGranularity) string {
 		// find what range cpm is in
 		if cpm >= config.Ranges[i].Min && cpm <= config.Ranges[i].Max {
 			increment = config.Ranges[i].Increment
+			bucketMin = config.Ranges[i].Min
 		}
 	}
 
@@ -29,13 +30,13 @@ func GetPriceBucket(cpm float64, config openrtb_ext.PriceGranularity) string {
 		cpmStr = strconv.FormatFloat(bucketMax, 'f', precision, 64)
 	} else if increment > 0 {
 		// If increment exists, get cpm string value
-		cpmStr = getCpmTarget(cpm, increment, precision)
+		cpmStr = getCpmTarget(cpm, bucketMin, increment, precision)
 	}
 
 	return cpmStr
 }
 
-func getCpmTarget(cpm float64, increment float64, precision int) string {
-	roundedCPM := math.Floor(cpm/increment) * increment
+func getCpmTarget(cpm float64, bucketMin float64, increment float64, precision int) string {
+	roundedCPM := math.Floor((cpm-bucketMin)/increment)*increment + bucketMin
 	return strconv.FormatFloat(roundedCPM, 'f', precision, 64)
 }
diff --git a/go.mod b/go.mod
index 90f87507..f3e3cdab 100644
--- a/go.mod
+++ b/go.mod
@@ -30,8 +30,8 @@ require (
 	github.com/vrischmann/go-metrics-influxdb v0.1.1
 	github.com/xeipuuv/gojsonschema v1.2.0
 	github.com/yudai/gojsondiff v1.0.0
-	golang.org/x/net v0.0.0-20220909164309-bea034e7d591
-	golang.org/x/text v0.3.7
+	golang.org/x/net v0.5.0
+	golang.org/x/text v0.6.0
 	google.golang.org/grpc v1.46.2
 	gopkg.in/evanphx/json-patch.v4 v4.12.0
 	gopkg.in/yaml.v3 v3.0.1
@@ -67,7 +67,7 @@ require (
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
 	github.com/yudai/pp v2.0.1+incompatible // indirect
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
-	golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
+	golang.org/x/sys v0.4.0 // indirect
 	google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
 	google.golang.org/protobuf v1.28.0 // indirect
 	gopkg.in/ini.v1 v1.66.4 // indirect
diff --git a/go.sum b/go.sum
index 30f3017b..2e795639 100644
--- a/go.sum
+++ b/go.sum
@@ -29,18 +29,12 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW
 cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
 cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
 cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
-cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
 cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
 cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
-cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
-cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
-cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
-cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
 cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
@@ -90,7 +84,6 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
 github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@@ -118,13 +111,11 @@ github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -142,7 +133,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
-github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
@@ -221,7 +211,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -249,14 +238,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
-github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
-github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
-github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
 github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
-github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
 github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@@ -266,7 +250,6 @@ github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9
 github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
 github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
-github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@@ -295,7 +278,6 @@ github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn
 github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
 github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
 github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
-github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
 github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
 github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
 github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
@@ -329,11 +311,9 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
@@ -423,7 +403,6 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
 github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -450,7 +429,6 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
 github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -458,7 +436,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
 github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
-github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
 github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -520,12 +497,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
-go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
 go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
-go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=
-go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -637,13 +610,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
-golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
+golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -661,9 +629,6 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -675,7 +640,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -755,18 +719,11 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
+golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -778,8 +735,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -843,8 +801,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -878,14 +834,6 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr
 google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
 google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
 google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
-google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
-google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
-google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
-google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
-google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
-google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
-google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
-google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -959,20 +907,7 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
-google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I=
 google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@@ -1005,7 +940,6 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
 google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
 google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
 google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
@@ -1052,7 +986,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1065,4 +998,3 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 8f0fce6e..e6b20b25 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -136,6 +136,7 @@ const (
 	BidderBlue              BidderName = "blue"
 	BidderBmtm              BidderName = "bmtm"
 	BidderBoldwin           BidderName = "boldwin"
+	BidderBrave             BidderName = "brave"
 	BidderBrightroll        BidderName = "brightroll"
 	BidderCcx               BidderName = "ccx"
 	BidderCoinzilla         BidderName = "coinzilla"
@@ -310,6 +311,7 @@ func CoreBidderNames() []BidderName {
 		BidderBlue,
 		BidderBmtm,
 		BidderBoldwin,
+		BidderBrave,
 		BidderBrightroll,
 		BidderCcx,
 		BidderCoinzilla,
diff --git a/openrtb_ext/imp_brave.go b/openrtb_ext/imp_brave.go
new file mode 100644
index 00000000..2c7a0eaf
--- /dev/null
+++ b/openrtb_ext/imp_brave.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ExtImpBrave struct {
+	PlacementID string `json:"placementId"`
+}
diff --git a/static/bidder-info/appnexus.yaml b/static/bidder-info/appnexus.yaml
index 06afe454..6686e7ac 100644
--- a/static/bidder-info/appnexus.yaml
+++ b/static/bidder-info/appnexus.yaml
@@ -1,7 +1,7 @@
 endpoint: "http://ib.adnxs.com/openrtb2"
 platform_id: "5"
 maintainer:
-  email: "prebid-server@xandr.com"
+  email: "prebid@microsoft.com"
 gvlVendorID: 32
 capabilities:
   app:
diff --git a/static/bidder-info/brave.yaml b/static/bidder-info/brave.yaml
new file mode 100644
index 00000000..1e4b7ced
--- /dev/null
+++ b/static/bidder-info/brave.yaml
@@ -0,0 +1,15 @@
+endpoint: "http://point.braveglobal.tv/?t=3&partner={{.PublisherID}}"
+maintainer:
+  email: "support@thebrave.io"
+gvlVendorID: 869
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
diff --git a/static/bidder-info/synacormedia.yaml b/static/bidder-info/synacormedia.yaml
index 47d66458..c19d7674 100644
--- a/static/bidder-info/synacormedia.yaml
+++ b/static/bidder-info/synacormedia.yaml
@@ -12,11 +12,4 @@ capabilities:
       - banner
       - video
 userSync:
-  key: "imds"
-  supportCors: true
-  iframe:
-    url: "https://ad-cdn.technoratimedia.com/html/usersync.html?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
-    userMacro: "[USER_ID]"
-  redirect:
-    url: "https://sync.technoratimedia.com/services?srv=cs&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
-    userMacro: "[USER_ID]"
+  key: "imds"
\ No newline at end of file
diff --git a/static/bidder-params/brave.json b/static/bidder-params/brave.json
new file mode 100644
index 00000000..8e913982
--- /dev/null
+++ b/static/bidder-params/brave.json
@@ -0,0 +1,13 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Brave Adapter Params",
+  "description": "A schema which validates params accepted by the Brave adapter",
+  "type": "object",
+  "properties": {
+    "placementId": {
+      "type": "string",
+      "description": "Unique Placement ID of publisher"
+    }
+  },
+  "required": ["placementId"]
+}
\ No newline at end of file


Test patch:
diff --git a/adapters/adnuntius/adnuntius_test.go b/adapters/adnuntius/adnuntius_test.go
index 27e006e1..9c431c2a 100644
--- a/adapters/adnuntius/adnuntius_test.go
+++ b/adapters/adnuntius/adnuntius_test.go
@@ -13,7 +13,10 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderAdnuntius, config.Adapter{
-		Endpoint: "http://whatever.url"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+		Endpoint:         "http://whatever.url",
+		ExtraAdapterInfo: "http://gdpr.url",
+	},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
@@ -43,5 +46,4 @@ func AssignDefaultValues(bidder adapters.Bidder) {
 	bidderAdnuntius.time = &FakeTime{
 		time: time.Date(2016, 1, 1, 12, 30, 15, 0, time.UTC),
 	}
-	bidderAdnuntius.extraInfo = "http://gdpr.url"
 }
diff --git a/adapters/brave/brave_test.go b/adapters/brave/brave_test.go
new file mode 100644
index 00000000..e8d0a474
--- /dev/null
+++ b/adapters/brave/brave_test.go
@@ -0,0 +1,30 @@
+package brave
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/adapters/adapterstest"
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderBrave, config.Adapter{
+		Endpoint: "http://point.braveglobal.tv/?t=3&partner={{.PublisherID}}"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "bravetest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderBrave, config.Adapter{
+		Endpoint: "{{Malformed}}"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	assert.Error(t, buildErr)
+}
diff --git a/adapters/brave/bravetest/exemplary/banner-app.json b/adapters/brave/bravetest/exemplary/banner-app.json
new file mode 100644
index 00000000..71a30978
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/banner-app.json
@@ -0,0 +1,140 @@
+{
+    "mockBidRequest": {
+      "id": "request-id",
+      "app": {
+        "publisher": {
+          "id": "123456789"
+        },
+        "cat": [
+          "IAB9-1"
+        ],
+        "bundle": "com.app.test",
+        "name": "Test App",
+        "id": "12345678"
+      },
+      "device": {
+        "ua": "useragent",
+        "ip": "100.100.100.100",
+        "language": "en"
+      },
+      "tmax": 1000,
+      "user": {
+        "id": "some-user"
+      },
+      "imp": [
+        {
+          "id": "impression-id",
+          "tagid": "tid",
+          "banner": {
+            "w":320,
+            "h":50
+          },
+          "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest": {
+          "headers": {
+            "Content-Type": [
+              "application/json;charset=utf-8"
+            ],
+            "Accept": [
+              "application/json"
+            ]
+          },
+          "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+          "body": {
+            "id": "request-id",
+            "device": {
+              "ua": "useragent",
+              "ip": "100.100.100.100",
+              "language": "en"
+            },
+            "imp": [
+              {
+                "id": "impression-id",
+                "banner": {
+                    "w":320,
+                    "h":50
+                },
+                "tagid": "tid"
+              }
+            ],
+            "app": {
+              "publisher": {
+                "id": "123456789"
+              },
+              "cat": [
+                "IAB9-1"
+              ],
+              "bundle": "com.app.test",
+              "name": "Test App",
+              "id": "12345678"
+            },
+            "user": {
+              "id": "some-user"
+            },
+            "tmax": 1000
+          }
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "123456789",
+                    "impid": "impression-id",
+                    "price": 2,
+                    "adm": "adm code",
+                    "adomain": [
+                      "testdomain.com"
+                    ],
+                    "crid": "100",
+                    "w":320,
+                    "h":50
+                  }
+                ],
+              "type": "banner",
+                "seat": "brave"
+              }
+            ],
+            "cur": "USD",
+            "ext": {
+              "responsetimemillis": {
+                "brave": 120
+              },
+              "tmaxrequest": 1000
+            }
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids": [{
+          "bid": {
+            "id": "123456789",
+            "impid": "impression-id",
+            "price": 2,
+            "adm": "adm code",
+            "adomain": [
+              "testdomain.com"
+            ],
+            "crid": "100",
+            "w":320,
+            "h":50
+          },
+          "type": "banner"
+        }]
+      }
+    ]
+  }
diff --git a/adapters/brave/bravetest/exemplary/banner-web.json b/adapters/brave/bravetest/exemplary/banner-web.json
new file mode 100644
index 00000000..0dd6dde0
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/banner-web.json
@@ -0,0 +1,138 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "site": {
+      "page": "test.com/page",
+      "domain": "test.com",
+      "cat": [
+        "IAB9-1"
+      ],
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "banner": {
+          "w":320,
+          "h":50
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "f897beb0daba0253d8e59a098eef9311"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "banner": {
+                  "w":320,
+                  "h":50
+              },
+              "tagid": "tid"
+            }
+          ],
+          "site": {
+            "page": "test.com/page",
+            "domain": "test.com",
+            "cat": [
+              "IAB9-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "123456789",
+                  "impid": "impression-id",
+                  "price": 2,
+                  "adm": "adm code",
+                  "adomain": [
+                    "testdomain.com"
+                  ],
+                  "crid": "100",
+                  "w":320,
+                  "h":50
+                }
+              ],
+            "type": "banner",
+              "seat": "brave"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "brave": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "123456789",
+          "impid": "impression-id",
+          "price": 2,
+          "adm": "adm code",
+          "adomain": [
+            "testdomain.com"
+          ],
+          "crid": "100",
+          "w":320,
+          "h":50
+        },
+        "type": "banner"
+      }]
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/exemplary/native-app.json b/adapters/brave/bravetest/exemplary/native-app.json
new file mode 100644
index 00000000..05ac5aa8
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/native-app.json
@@ -0,0 +1,137 @@
+{
+    "mockBidRequest": {
+      "id": "request-id",
+      "app": {
+        "publisher": {
+          "id": "123456789"
+        },
+        "cat": [
+          "IAB9-1"
+        ],
+        "bundle": "com.app.test",
+        "name": "Test App",
+        "id": "12345678"
+      },
+      "device": {
+        "ua": "useragent",
+        "ip": "100.100.100.100",
+        "language": "en"
+      },
+      "tmax": 1000,
+      "user": {
+        "id": "some-user"
+      },
+      "imp": [
+        {
+          "id": "impression-id",
+          "tagid": "tid",
+          "native": {
+            "ver":"1.1",
+            "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+          },
+	        "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest": {
+            "headers": {
+              "Content-Type": [
+                "application/json;charset=utf-8"
+              ],
+              "Accept": [
+                "application/json"
+              ]
+            },
+            "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+            "body": {
+              "id": "request-id",
+              "device": {
+                "ua": "useragent",
+                "ip": "100.100.100.100",
+                "language": "en"
+              },
+              "imp": [
+                {
+                  "id": "impression-id",
+                  "native": {
+                    "ver":"1.1",
+                    "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+                  },
+                  "tagid": "tid"
+                }
+              ],
+              "app": {
+                "publisher": {
+                  "id": "123456789"
+                },
+                "cat": [
+                  "IAB9-1"
+                ],
+                "bundle": "com.app.test",
+                "name": "Test App",
+                "id": "12345678"
+              },
+              "user": {
+                "id": "some-user"
+              },
+              "tmax": 1000
+            }
+        },
+        "mockResponse": {
+
+          "status": 200,
+          "body": {
+            "id": "resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "123456789",
+                    "impid": "impression-id",
+                    "price": 2,
+                    "adm": "adm code",
+                    "adomain": [
+                      "testdomain.com"
+                    ],
+                    "crid": "100"
+                  }
+                ],
+                "type": "native",
+                "seat": "brave"
+              }
+            ],
+            "cur": "USD",
+            "ext": {
+              "responsetimemillis": {
+                "brave": 120
+              },
+              "tmaxrequest": 1000
+            }
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids": [{
+          "bid": {
+            "id": "123456789",
+            "impid": "impression-id",
+            "price": 2,
+            "adm": "adm code",
+            "adomain": [
+              "testdomain.com"
+            ],
+            "crid": "100"
+          },
+          "type": "native"
+        }]
+      }
+    ]
+  }
diff --git a/adapters/brave/bravetest/exemplary/native-web.json b/adapters/brave/bravetest/exemplary/native-web.json
new file mode 100644
index 00000000..01126e46
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/native-web.json
@@ -0,0 +1,135 @@
+{
+    "mockBidRequest": {
+      "id": "request-id",
+      "site": {
+        "page": "test.com/page",
+        "domain": "test.com",
+        "cat": [
+          "IAB9-1"
+        ],
+        "publisher": {
+          "id": "123456789"
+        }
+      },
+      "device": {
+        "ua": "useragent",
+        "ip": "100.100.100.100",
+        "language": "en"
+      },
+      "tmax": 1000,
+      "user": {
+        "id": "some-user"
+      },
+      "imp": [
+        {
+          "id": "impression-id",
+          "tagid": "tid",
+          "native": {
+            "ver":"1.1",
+            "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+          },
+	        "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+    {
+        "expectedRequest": {
+            "headers": {
+              "Content-Type": [
+                "application/json;charset=utf-8"
+              ],
+              "Accept": [
+                "application/json"
+              ]
+            },
+            "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+            "body": {
+              "id": "request-id",
+              "device": {
+                "ua": "useragent",
+                "ip": "100.100.100.100",
+                "language": "en"
+              },
+              "imp": [
+                {
+                  "id": "impression-id",
+                  "native": {
+                    "ver":"1.1",
+                    "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+                  },
+                  "tagid": "tid"
+                }
+              ],
+              "site": {
+                "page": "test.com/page",
+                "domain": "test.com",
+                "cat": [
+                  "IAB9-1"
+                ],
+                "publisher": {
+                  "id": "123456789"
+                }
+              },
+              "user": {
+                "id": "some-user"
+              },
+              "tmax": 1000
+            }
+        },
+        "mockResponse": {
+
+          "status": 200,
+          "body": {
+            "id": "resp-id",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "123456789",
+                    "impid": "impression-id",
+                    "price": 2,
+                    "adm": "adm code",
+                    "adomain": [
+                      "testdomain.com"
+                    ],
+                    "crid": "100"
+                  }
+                ],
+                "type": "native",
+                "seat": "brave"
+              }
+            ],
+            "cur": "USD",
+            "ext": {
+              "responsetimemillis": {
+                "brave": 120
+              },
+              "tmaxrequest": 1000
+            }
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "bids": [{
+          "bid": {
+            "id": "123456789",
+            "impid": "impression-id",
+            "price": 2,
+            "adm": "adm code",
+            "adomain": [
+              "testdomain.com"
+            ],
+            "crid": "100"
+          },
+          "type": "native"
+        }]
+      }
+    ]
+  }
diff --git a/adapters/brave/bravetest/exemplary/video-app.json b/adapters/brave/bravetest/exemplary/video-app.json
new file mode 100644
index 00000000..7f415109
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/video-app.json
@@ -0,0 +1,150 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 30,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "tid"
+            }
+          ],
+          "app": {
+            "publisher": {
+              "id": "123456789"
+            },
+            "cat": [
+              "IAB9-1"
+            ],
+            "bundle": "com.app.test",
+            "name": "Test App",
+            "id": "12345678"
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "123456789",
+                  "impid": "impression-id",
+                  "price": 2,
+                  "adm": "adm code",
+                  "adomain": [
+                    "testdomain.com"
+                  ],
+                  "crid": "100",
+                  "w": 1280,
+                  "h": 720
+                }
+              ],
+            "type": "video",
+            "seat": "brave"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "brave": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "123456789",
+          "impid": "impression-id",
+          "price": 2,
+          "adm": "adm code",
+          "adomain": [
+            "testdomain.com"
+          ],
+          "crid": "100",
+          "w": 1280,
+          "h": 720
+        },
+        "type": "video"
+      }]
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/exemplary/video-web.json b/adapters/brave/bravetest/exemplary/video-web.json
new file mode 100644
index 00000000..5da8ff35
--- /dev/null
+++ b/adapters/brave/bravetest/exemplary/video-web.json
@@ -0,0 +1,148 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "site": {
+      "page": "test.com/page",
+      "domain": "test.com",
+      "cat": [
+        "IAB9-1"
+      ],
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 30,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "tid"
+            }
+          ],
+          "site": {
+            "page": "test.com/page",
+            "domain": "test.com",
+            "cat": [
+              "IAB9-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "123456789",
+                  "impid": "impression-id",
+                  "price": 2,
+                  "adm": "adm code",
+                  "adomain": [
+                    "testdomain.com"
+                  ],
+                  "crid": "100",
+                  "w": 1280,
+                  "h": 720
+                }
+              ],
+            "type": "video",
+            "seat": "brave"
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "brave": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [{
+        "bid": {
+          "id": "123456789",
+          "impid": "impression-id",
+          "price": 2,
+          "adm": "adm code",
+          "adomain": [
+            "testdomain.com"
+          ],
+          "crid": "100",
+          "w": 1280,
+          "h": 720
+        },
+        "type": "video"
+      }]
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/empty-seatbid-array.json b/adapters/brave/bravetest/supplemental/empty-seatbid-array.json
new file mode 100644
index 00000000..c407c822
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/empty-seatbid-array.json
@@ -0,0 +1,121 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "site": {
+      "page": "test.com/page",
+      "domain": "test.com",
+      "cat": [
+        "IAB9-1"
+      ],
+      "publisher": {
+        "id": "123456789"
+      }
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+        "body": {
+          "id": "request-id",
+          "device": {
+            "ua": "useragent",
+            "ip": "100.100.100.100",
+            "language": "en"
+          },
+          "imp": [
+            {
+              "id": "impression-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ],
+                "minduration": 30,
+                "maxduration": 150,
+                "w": 640,
+                "h": 480
+              },
+              "tagid": "tid"
+            }
+          ],
+          "site": {
+            "page": "test.com/page",
+            "domain": "test.com",
+            "cat": [
+              "IAB9-1"
+            ],
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "id": "some-user"
+          },
+          "tmax": 1000
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "resp-id",
+          "seatbid": [
+          ],
+          "cur": "USD",
+          "ext": {
+            "responsetimemillis": {
+              "brave": 120
+            },
+            "tmaxrequest": 1000
+          }
+        }
+      }
+    }
+  ],
+    "mockResponse": {
+      "status": 200,
+      "body": "invalid response"
+    },
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Empty SeatBid array",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/invalid-brave-ext-object.json b/adapters/brave/bravetest/supplemental/invalid-brave-ext-object.json
new file mode 100644
index 00000000..899834e6
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/invalid-brave-ext-object.json
@@ -0,0 +1,29 @@
+{
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "ext.bidder not provided",
+      "comparison": "literal"
+    }
+  ],
+  "mockBidRequest": {
+    "id": "request-id",
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": ["video/mp4"],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+        "ext": "invalid data"
+      }
+    ],
+    "site": {
+      "page": "test.com"
+    }
+  },
+  "httpCalls": []
+}
diff --git a/adapters/brave/bravetest/supplemental/invalid-response.json b/adapters/brave/bravetest/supplemental/invalid-response.json
new file mode 100644
index 00000000..972c2b30
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/invalid-response.json
@@ -0,0 +1,97 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "banner": {
+          "w":320,
+          "h":50
+        },
+        "ext": {
+          "bidder": {
+            "placementId": "f897beb0daba0253d8e59a098eef9311"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "banner": {
+                "w":320,
+                "h":50
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": "invalid response"
+    }
+  }],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad Server Response",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/status-code-bad-request.json b/adapters/brave/bravetest/supplemental/status-code-bad-request.json
new file mode 100644
index 00000000..e80273a7
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/status-code-bad-request.json
@@ -0,0 +1,108 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 400
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/status-code-no-content.json b/adapters/brave/bravetest/supplemental/status-code-no-content.json
new file mode 100644
index 00000000..eb369bd8
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/status-code-no-content.json
@@ -0,0 +1,108 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 204
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "No bid",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/status-code-other-error.json b/adapters/brave/bravetest/supplemental/status-code-other-error.json
new file mode 100644
index 00000000..bd68ee85
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/status-code-other-error.json
@@ -0,0 +1,108 @@
+
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 306
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Something went wrong, please contact your Account Manager. Status Code: [ 306 ] ",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/bravetest/supplemental/status-code-service-unavailable.json b/adapters/brave/bravetest/supplemental/status-code-service-unavailable.json
new file mode 100644
index 00000000..f66347d8
--- /dev/null
+++ b/adapters/brave/bravetest/supplemental/status-code-service-unavailable.json
@@ -0,0 +1,107 @@
+{
+  "mockBidRequest": {
+    "id": "request-id",
+    "app": {
+      "publisher": {
+        "id": "123456789"
+      },
+      "cat": [
+        "IAB9-1"
+      ],
+      "bundle": "com.app.test",
+      "name": "Test App",
+      "id": "12345678"
+    },
+    "device": {
+      "ua": "useragent",
+      "ip": "100.100.100.100",
+      "language": "en"
+    },
+    "tmax": 1000,
+    "user": {
+      "id": "some-user"
+    },
+    "imp": [
+      {
+        "id": "impression-id",
+        "tagid": "tid",
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ],
+          "w": 640,
+          "h": 480,
+          "minduration": 30,
+          "maxduration": 150
+        },
+	      "ext": {
+            "bidder": {
+              "placementId": "f897beb0daba0253d8e59a098eef9311"
+            }
+          }
+      }
+    ]
+  },
+
+  "httpCalls": [{
+    "expectedRequest": {
+      "headers": {
+        "Content-Type": [
+          "application/json;charset=utf-8"
+        ],
+        "Accept": [
+          "application/json"
+        ]
+      },
+      "uri": "http://point.braveglobal.tv/?t=3&partner=f897beb0daba0253d8e59a098eef9311",
+      "body": {
+        "id": "request-id",
+        "device": {
+          "ua": "useragent",
+          "ip": "100.100.100.100",
+          "language": "en"
+        },
+        "imp": [
+          {
+            "id": "impression-id",
+            "video": {
+              "mimes": [
+                "video/mp4"
+              ],
+              "minduration": 30,
+              "maxduration": 150,
+              "w": 640,
+              "h": 480
+            },
+            "tagid": "tid"
+          }
+        ],
+        "app": {
+          "publisher": {
+            "id": "123456789"
+          },
+          "cat": [
+            "IAB9-1"
+          ],
+          "bundle": "com.app.test",
+          "name": "Test App",
+          "id": "12345678"
+        },
+        "user": {
+          "id": "some-user"
+        },
+        "tmax": 1000
+      }
+    },
+    "mockResponse": {
+      "status": 503
+    }
+  }],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Service Unavailable. Status Code: [ 503 ] ",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/brave/params_test.go b/adapters/brave/params_test.go
new file mode 100644
index 00000000..1120a13c
--- /dev/null
+++ b/adapters/brave/params_test.go
@@ -0,0 +1,51 @@
+package brave
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderBrave, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected Brave params: %s", validParam)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderBrave, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
+
+var validParams = []string{
+	`{ "placementId": "f897beb0daba0253d8e59a098eef9311" }`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`,
+	`{ "placementid": "f897beb0daba0253d8e59a098eef9311" }`,
+	`{ "PlacementId": "f897beb0daba0253d8e59a098eef9311" }`,
+}
diff --git a/adapters/openx/openxtest/exemplary/fledge-no-bids.json b/adapters/openx/openxtest/exemplary/fledge-no-bids.json
new file mode 100644
index 00000000..5c09b9ae
--- /dev/null
+++ b/adapters/openx/openxtest/exemplary/fledge-no-bids.json
@@ -0,0 +1,88 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "ae": 1,
+          "bidder": {
+            "unit": "539439964",
+            "delDomain": "se-demo-d.openx.net"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://rtb.openx.net/prebid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}]
+              },
+              "tagid": "539439964",
+              "ext": {
+                "ae": 1
+              }
+            }
+          ],
+          "ext": {
+            "bc": "hb_pbs_1.0.0",
+            "delDomain": "se-demo-d.openx.net"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "cur": "USD",
+          "nbr": 1,
+          "ext": {
+            "fledge_auction_configs": {
+              "test-request-id": {
+                "seller": "openx.com",
+                "interestGroupBuyers": ["buyer1.com"],
+                "sellerTimeout": 0,
+                "perBuyerSignals": {
+                  "buyer1.com": [1,"two",3,4, {}]
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [],
+      "fledgeauctionconfigs": [
+        {
+          "impid": "test-request-id",
+          "bidder": "openx",
+          "config": {
+            "seller": "openx.com",
+            "interestGroupBuyers": ["buyer1.com"],
+            "sellerTimeout": 0,
+            "perBuyerSignals": {
+              "buyer1.com": [1,"two",3,4, {}]
+            }
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/openx/openxtest/exemplary/fledge.json b/adapters/openx/openxtest/exemplary/fledge.json
new file mode 100644
index 00000000..9c8074b3
--- /dev/null
+++ b/adapters/openx/openxtest/exemplary/fledge.json
@@ -0,0 +1,114 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [{"w": 728, "h": 90}]
+        },
+        "ext": {
+          "ae": 1,
+          "bidder": {
+            "unit": "539439964",
+            "delDomain": "se-demo-d.openx.net"
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://rtb.openx.net/prebid",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [{"w": 728, "h": 90}]
+              },
+              "tagid": "539439964",
+              "ext": {
+                "ae": 1
+              }
+            }
+          ],
+          "ext": {
+            "bc": "hb_pbs_1.0.0",
+            "delDomain": "se-demo-d.openx.net"
+          }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "seat": "openx",
+              "bid": [{
+                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                "impid": "test-imp-id",
+                "price": 0.500000,
+                "adm": "some-test-ad",
+                "crid": "crid_10",
+                "h": 90,
+                "w": 728
+              }]
+            }
+          ],
+          "cur": "USD",
+          "ext": {
+            "fledge_auction_configs": {
+              "test-request-id": {
+                "seller": "openx.com",
+                "interestGroupBuyers": ["buyer1.com"],
+                "sellerTimeout": 0,
+                "perBuyerSignals": {
+                  "buyer1.com": [1,"two",3,4, {}]
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+            "impid": "test-imp-id",
+            "price": 0.5,
+            "adm": "some-test-ad",
+            "crid": "crid_10",
+            "w": 728,
+            "h": 90
+          },
+          "type": "banner"
+        }
+      ],
+      "fledgeauctionconfigs": [
+        {
+          "impid": "test-request-id",
+          "bidder": "openx",
+          "config": {
+            "seller": "openx.com",
+            "interestGroupBuyers": ["buyer1.com"],
+            "sellerTimeout": 0,
+            "perBuyerSignals": {
+              "buyer1.com": [1,"two",3,4, {}]
+            }
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/exchange/price_granularity_test.go b/exchange/price_granularity_test.go
index 13840838..6349713e 100644
--- a/exchange/price_granularity_test.go
+++ b/exchange/price_granularity_test.go
@@ -30,6 +30,22 @@ func TestGetPriceBucketString(t *testing.T) {
 		},
 	}
 
+	custom2 := openrtb_ext.PriceGranularity{
+		Precision: 2,
+		Ranges: []openrtb_ext.GranularityRange{
+			{
+				Min:       0.0,
+				Max:       1.5,
+				Increment: 1.0,
+			},
+			{
+				Min:       1.5,
+				Max:       10.0,
+				Increment: 1.2,
+			},
+		},
+	}
+
 	// Define test cases
 	type aTest struct {
 		granularityId       string
@@ -51,6 +67,7 @@ func TestGetPriceBucketString(t *testing.T) {
 				{"auto", auto, "1.85"},
 				{"dense", dense, "1.87"},
 				{"custom1", custom1, "1.86"},
+				{"custom2", custom2, "1.50"},
 			},
 		},
 		{
@@ -63,6 +80,15 @@ func TestGetPriceBucketString(t *testing.T) {
 				{"auto", auto, "5.70"},
 				{"dense", dense, "5.70"},
 				{"custom1", custom1, "5.70"},
+				{"custom2", custom2, "5.10"},
+			},
+		},
+		{
+			groupDesc: "cpm equal the max for custom granularity",
+			cpm:       10,
+			testCases: []aTest{
+				{"custom1", custom1, "10.00"},
+				{"custom2", custom2, "9.90"},
 			},
 		},
 		{