From b8898cd437254271c12e96d842ca16a2be7eaceb Mon Sep 17 00:00:00 2001
From: Ad Generation
Date: Fri, 24 Apr 2020 00:45:45 +0900
Subject: [PATCH] Ad Generation Adapter Integration. (#1253)
* AdGeneration Integration.
* update AdGeneration adapter.
fix: some methods of the adgAdapter replace to functions.
fix: unmarshal functions return a pointer.
fix: header is defined once.
fix: return when imps is appended
* update AdGeneration Adapter.
add: Added a comment in usersync.
add: Added a test for parameters whose ID does not exist in params_test.
change: Change to query creation by net/url. Added getRawQuery Test.
fix: Changed variable names related to bidRequest.
---
adapters/adgeneration/adgeneration.go | 260 ++++++++++++++++++
adapters/adgeneration/adgeneration_test.go | 176 ++++++++++++
.../exemplary/single-banner.json | 151 ++++++++++
.../adgenerationtest/params/race/banner.json | 3 +
.../supplemental/204-bid-response.json | 72 +++++
.../supplemental/400-bid-response.json | 77 ++++++
.../supplemental/invalid-adg-param.json | 31 +++
.../supplemental/no-bid-response.json | 89 ++++++
adapters/adgeneration/params_test.go | 47 ++++
config/config.go | 2 +
exchange/adapter_map.go | 2 +
openrtb_ext/bidders.go | 2 +
openrtb_ext/imp_adgeneration.go | 5 +
static/bidder-info/adgeneration.yaml | 10 +
static/bidder-params/adgeneration.json | 15 +
usersync/usersyncers/syncer_test.go | 13 +-
16 files changed, 949 insertions(+), 6 deletions(-)
create mode 100644 adapters/adgeneration/adgeneration.go
create mode 100644 adapters/adgeneration/adgeneration_test.go
create mode 100644 adapters/adgeneration/adgenerationtest/exemplary/single-banner.json
create mode 100644 adapters/adgeneration/adgenerationtest/params/race/banner.json
create mode 100644 adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json
create mode 100644 adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json
create mode 100644 adapters/adgeneration/adgenerationtest/supplemental/invalid-adg-param.json
create mode 100644 adapters/adgeneration/adgenerationtest/supplemental/no-bid-response.json
create mode 100644 adapters/adgeneration/params_test.go
create mode 100644 openrtb_ext/imp_adgeneration.go
create mode 100644 static/bidder-info/adgeneration.yaml
create mode 100644 static/bidder-params/adgeneration.json
diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go
new file mode 100644
index 00000000000..4b1215dea9d
--- /dev/null
+++ b/adapters/adgeneration/adgeneration.go
@@ -0,0 +1,260 @@
+package adgeneration
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/mxmCherry/openrtb"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type AdgenerationAdapter struct {
+ endpoint string
+ version string
+ defaultCurrency string
+}
+
+// Server Responses
+type adgServerResponse struct {
+ Locationid string `json:"locationid"`
+ Dealid string `json:"dealid"`
+ Ad string `json:"ad"`
+ Beacon string `json:"beacon"`
+ Beaconurl string `json:"beaconurl"`
+ Cpm float64 `jsons:"cpm"`
+ Creativeid string `json:"creativeid"`
+ H uint64 `json:"h"`
+ W uint64 `json:"w"`
+ Ttl uint64 `json:"ttl"`
+ Vastxml string `json:"vastxml,omitempty"`
+ LandingUrl string `json:"landing_url"`
+ Scheduleid string `json:"scheduleid"`
+ Results []interface{} `json:"results"`
+}
+
+func (adg *AdgenerationAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ numRequests := len(request.Imp)
+ var errs []error
+
+ if numRequests == 0 {
+ errs = append(errs, &errortypes.BadInput{
+ Message: "No impression in the bid request",
+ })
+ return nil, errs
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ bidRequestArray := make([]*adapters.RequestData, 0, numRequests)
+
+ for index := 0; index < numRequests; index++ {
+ bidRequestUri, err := adg.getRequestUri(request, index)
+ if err != nil {
+ errs = append(errs, err)
+ return nil, errs
+ }
+ bidRequest := &adapters.RequestData{
+ Method: "GET",
+ Uri: bidRequestUri,
+ Body: nil,
+ Headers: headers,
+ }
+ bidRequestArray = append(bidRequestArray, bidRequest)
+ }
+
+ return bidRequestArray, errs
+}
+
+func (adg *AdgenerationAdapter) getRequestUri(request *openrtb.BidRequest, index int) (string, error) {
+ imp := request.Imp[index]
+ adgExt, err := unmarshalExtImpAdgeneration(&imp)
+ if err != nil {
+ return "", &errortypes.BadInput{
+ Message: err.Error(),
+ }
+ }
+ uriObj, err := url.Parse(adg.endpoint)
+ if err != nil {
+ return "", &errortypes.BadInput{
+ Message: err.Error(),
+ }
+ }
+ v := adg.getRawQuery(adgExt.Id, request, &imp)
+ uriObj.RawQuery = v.Encode()
+ return uriObj.String(), err
+}
+
+func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb.BidRequest, imp *openrtb.Imp) *url.Values {
+ v := url.Values{}
+ v.Set("posall", "SSPLOC")
+ v.Set("id", id)
+ v.Set("sdktype", "0")
+ v.Set("hb", "true")
+ v.Set("t", "json3")
+ v.Set("currency", adg.getCurrency(request))
+ v.Set("sdkname", "prebidserver")
+ v.Set("adapterver", adg.version)
+ adSize := getSizes(imp)
+ if adSize != "" {
+ v.Set("size", adSize)
+ }
+ if request.Site != nil && request.Site.Page != "" {
+ v.Set("tp", request.Site.Page)
+ }
+ return &v
+}
+
+func unmarshalExtImpAdgeneration(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdgeneration, error) {
+ var bidderExt adapters.ExtImpBidder
+ var adgExt openrtb_ext.ExtImpAdgeneration
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, err
+ }
+ if err := json.Unmarshal(bidderExt.Bidder, &adgExt); err != nil {
+ return nil, err
+ }
+ if adgExt.Id == "" {
+ return nil, errors.New("No Location ID in ExtImpAdgeneration.")
+ }
+ return &adgExt, nil
+}
+
+func getSizes(imp *openrtb.Imp) string {
+ if imp.Banner == nil || len(imp.Banner.Format) == 0 {
+ return ""
+ }
+ var sizeStr string
+ for _, v := range imp.Banner.Format {
+ sizeStr += strconv.FormatUint(v.W, 10) + "×" + strconv.FormatUint(v.H, 10) + ","
+ }
+ if len(sizeStr) > 0 && strings.LastIndex(sizeStr, ",") == len(sizeStr)-1 {
+ sizeStr = sizeStr[:len(sizeStr)-1]
+ }
+ return sizeStr
+}
+
+func (adg *AdgenerationAdapter) getCurrency(request *openrtb.BidRequest) string {
+ if len(request.Cur) <= 0 {
+ return adg.defaultCurrency
+ } else {
+ for _, c := range request.Cur {
+ if adg.defaultCurrency == c {
+ return c
+ }
+ }
+ return request.Cur[0]
+ }
+}
+
+func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+ if response.StatusCode == http.StatusBadRequest {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+ }}
+ }
+ if response.StatusCode != http.StatusOK {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+ }}
+ }
+ var bidResp adgServerResponse
+ err := json.Unmarshal(response.Body, &bidResp)
+ if err != nil {
+ return nil, []error{err}
+ }
+ if len(bidResp.Results) <= 0 {
+ return nil, nil
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+ var impId string
+ var bitType openrtb_ext.BidType
+ var adm string
+ for _, v := range internalRequest.Imp {
+ adgExt, err := unmarshalExtImpAdgeneration(&v)
+ if err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: err.Error(),
+ },
+ }
+ }
+ if adgExt.Id == bidResp.Locationid {
+ impId = v.ID
+ bitType = openrtb_ext.BidTypeBanner
+ adm = createAd(&bidResp, impId)
+ bid := openrtb.Bid{
+ ID: bidResp.Locationid,
+ ImpID: impId,
+ AdM: adm,
+ Price: bidResp.Cpm,
+ W: bidResp.W,
+ H: bidResp.H,
+ CrID: bidResp.Creativeid,
+ DealID: bidResp.Dealid,
+ }
+
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: bitType,
+ })
+ return bidResponse, nil
+ }
+ }
+ return nil, nil
+}
+
+func createAd(body *adgServerResponse, impId string) string {
+ ad := body.Ad
+ if body.Vastxml != "" {
+ ad = "" + insertVASTMethod(impId, body.Vastxml) + ""
+ }
+ ad = appendChildToBody(ad, body.Beacon)
+ unwrappedAd := removeWrapper(ad)
+ if unwrappedAd != "" {
+ return unwrappedAd
+ }
+ return ad
+}
+
+func insertVASTMethod(bidId string, vastxml string) string {
+ rep := regexp.MustCompile(`/\r?\n/g`)
+ var replacedVastxml = rep.ReplaceAllString(vastxml, "")
+ return ""
+}
+
+func appendChildToBody(ad string, data string) string {
+ rep := regexp.MustCompile(`<\/\s?body>`)
+ return rep.ReplaceAllString(ad, data+"
")
+ lastBodyIndex := strings.LastIndex(ad, "", "", 1), "\n\n\n
\n\n\n\n
\n")
+}
+
+func removeWrapper(ad string) string {
+ bodyIndex := strings.Index(ad, "
")
+ if bodyIndex == -1 || lastBodyIndex == -1 {
+ return ""
+ }
+
+ str := strings.TrimSpace(strings.Replace(strings.Replace(ad[bodyIndex:lastBodyIndex], "
", "", 1))
+ return str
+}
+
+func NewAdgenerationAdapter(endpoint string) *AdgenerationAdapter {
+ return &AdgenerationAdapter{
+ endpoint,
+ "1.0.0",
+ "JPY",
+ }
+}
diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go
new file mode 100644
index 00000000000..e76995fc5e4
--- /dev/null
+++ b/adapters/adgeneration/adgeneration_test.go
@@ -0,0 +1,176 @@
+package adgeneration
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/mxmCherry/openrtb"
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "adgenerationtest", NewAdgenerationAdapter("https://d.socdm.com/adsv/v1"))
+}
+
+func TestgetRequestUri(t *testing.T) {
+ bidder := NewAdgenerationAdapter("https://d.socdm.com/adsv/v1")
+ // Test items
+ failedRequest := &openrtb.BidRequest{
+ ID: "test-failed-bid-request",
+ Imp: []openrtb.Imp{
+ {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{{ "id": "58278" }}`)},
+ {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"_bidder": { "id": "58278" }}`)},
+ {ID: "extImpAdgeneration-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "_id": "58278" }}`)},
+ },
+ Device: &openrtb.Device{UA: "testUA", IP: "testIP"},
+ Site: &openrtb.Site{Page: "https://supership.com"},
+ User: &openrtb.User{BuyerUID: "buyerID"},
+ }
+ successRequest := &openrtb.BidRequest{
+ ID: "test-success-bid-request",
+ Imp: []openrtb.Imp{
+ {ID: "bidRequest-success-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)},
+ },
+ Device: &openrtb.Device{UA: "testUA", IP: "testIP"},
+ Site: &openrtb.Site{Page: "https://supership.com"},
+ User: &openrtb.User{BuyerUID: "buyerID"},
+ }
+
+ numRequests := len(failedRequest.Imp)
+ for index := 0; index < numRequests; index++ {
+ httpRequests, err := bidder.getRequestUri(failedRequest, index)
+ if err == nil {
+ t.Errorf("getRequestUri: %v did not throw an error", failedRequest.Imp[index])
+ }
+ if httpRequests != "" {
+ t.Errorf("getRequestUri: %v did return Request: %s", failedRequest.Imp[index], httpRequests)
+ }
+ }
+ numRequests = len(successRequest.Imp)
+ for index := 0; index < numRequests; index++ {
+ // RequestUri Test.
+ httpRequests, err := bidder.getRequestUri(successRequest, index)
+ if err != nil {
+ t.Errorf("getRequestUri: %v did throw an error: %v", successRequest.Imp[index], err)
+ }
+ if httpRequests == "adapterver="+bidder.version+"¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html" {
+ t.Errorf("getRequestUri: %v did return Request: %s", successRequest.Imp[index], httpRequests)
+ }
+ // getRawQuery Test.
+ adgExt, err := unmarshalExtImpAdgeneration(&successRequest.Imp[index])
+ if err != nil {
+ t.Errorf("unmarshalExtImpAdgeneration: %v did throw an error: %v", successRequest.Imp[index], err)
+ }
+ rawQuery := bidder.getRawQuery(adgExt.Id, successRequest, &successRequest.Imp[index])
+ expectQueries := map[string]string{
+ "posall": "SSPLOC",
+ "id": adgExt.Id,
+ "sdktype": "0",
+ "hb": "true",
+ "currency": bidder.getCurrency(successRequest),
+ "sdkname": "prebidserver",
+ "adapterver": bidder.version,
+ "size": getSizes(&successRequest.Imp[index]),
+ "tp": successRequest.Site.Name,
+ }
+ for key, expectedValue := range expectQueries {
+ actualValue := rawQuery.Get(key)
+ if actualValue == "" {
+ if !(key == "size" || key == "tp") {
+ t.Errorf("getRawQuery: key %s is required value.", key)
+ }
+ }
+ if actualValue != expectedValue {
+ t.Errorf("getRawQuery: %s value does not match expected %s, actual %s", key, expectedValue, actualValue)
+ }
+ }
+ }
+}
+
+func TestGetSizes(t *testing.T) {
+ // Test items
+ var request *openrtb.Imp
+ var size string
+ multiFormatBanner := &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 320, H: 50}}}
+ noFormatBanner := &openrtb.Banner{Format: []openrtb.Format{}}
+ nativeFormat := &openrtb.Native{}
+
+ request = &openrtb.Imp{Banner: multiFormatBanner}
+ size = getSizes(request)
+ if size != "300×250,320×50" {
+ t.Errorf("%v does not match size.", multiFormatBanner)
+ }
+ request = &openrtb.Imp{Banner: noFormatBanner}
+ size = getSizes(request)
+ if size != "" {
+ t.Errorf("%v does not match size.", noFormatBanner)
+ }
+ request = &openrtb.Imp{Native: nativeFormat}
+ size = getSizes(request)
+ if size != "" {
+ t.Errorf("%v does not match size.", nativeFormat)
+ }
+}
+
+func TestGetCurrency(t *testing.T) {
+ bidder := NewAdgenerationAdapter("https://d.socdm.com/adsv/v1")
+ // Test items
+ var request *openrtb.BidRequest
+ var currency string
+ innerDefaultCur := []string{"USD", "JPY"}
+ usdCur := []string{"USD", "EUR"}
+
+ request = &openrtb.BidRequest{Cur: innerDefaultCur}
+ currency = bidder.getCurrency(request)
+ if currency != "JPY" {
+ t.Errorf("%v does not match currency.", innerDefaultCur)
+ }
+ request = &openrtb.BidRequest{Cur: usdCur}
+ currency = bidder.getCurrency(request)
+ if currency != "USD" {
+ t.Errorf("%v does not match currency.", usdCur)
+ }
+}
+
+func TestCreateAd(t *testing.T) {
+ // Test items
+ adgBannerImpId := "test-banner-imp"
+ adgBannerResponse := adgServerResponse{
+ Ad: "\n
\n\n\n
\n",
+ Beacon: "",
+ Beaconurl: "https://dummy-beacon.com",
+ Cpm: 50,
+ Creativeid: "DummyDsp_SdkTeam_supership.jp",
+ H: 300,
+ W: 250,
+ Ttl: 10,
+ LandingUrl: "",
+ Scheduleid: "111111",
+ }
+ matchBannerTag := "
\n\n
\n"
+
+ adgVastImpId := "test-vast-imp"
+ adgVastResponse := adgServerResponse{
+ Ad: "\n
\n\n\n
\n",
+ Beacon: "",
+ Beaconurl: "https://dummy-beacon.com",
+ Cpm: 50,
+ Creativeid: "DummyDsp_SdkTeam_supership.jp",
+ H: 300,
+ W: 250,
+ Ttl: 10,
+ LandingUrl: "",
+ Vastxml: "",
+ Scheduleid: "111111",
+ }
+ matchVastTag := ""
+
+ bannerAd := createAd(&adgBannerResponse, adgBannerImpId)
+ if bannerAd != matchBannerTag {
+ t.Errorf("%v does not match createAd.", adgBannerResponse)
+ }
+ vastAd := createAd(&adgVastResponse, adgVastImpId)
+ if vastAd != matchVastTag {
+ t.Errorf("%v does not match createAd.", adgVastResponse)
+ }
+}
diff --git a/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json
new file mode 100644
index 00000000000..d23a510bee5
--- /dev/null
+++ b/adapters/adgeneration/adgenerationtest/exemplary/single-banner.json
@@ -0,0 +1,151 @@
+{
+ "mockBidRequest":{
+ "id": "some-request-id",
+ "site": {
+ "page": "http://example.com/test.html"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "id": "58278"
+ }
+ }
+ }
+ ],
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "internalRequest": {
+ "id": "some-request-id",
+ "site": {
+ "page": "http://example.com/test.html"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "id": "58278"
+ }
+ }
+ }
+ ],
+ "tmax": 500
+ },
+ "expectedRequest":{
+ "uri": "https://d.socdm.com/adsv/v1?adapterver=1.0.0¤cy=JPY&hb=true&id=58278&posall=SSPLOC&sdkname=prebidserver&sdktype=0&size=300%C3%97250&t=json3&tp=http%3A%2F%2Fexample.com%2Ftest.html",
+ "headers": {
+ "Accept": [
+ "application/json"
+ ],
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ]
+ }
+ },
+ "mockResponse":{
+ "status": 200,
+ "body": {
+ "ad": "\n \n \n