Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration Type Implementation #2111

Merged
merged 12 commits into from
Feb 14, 2022
15 changes: 8 additions & 7 deletions analytics/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ const (
)

type EventRequest struct {
Type EventType `json:"type,omitempty"`
Format ResponseFormat `json:"format,omitempty"`
Analytics Analytics `json:"analytics,omitempty"`
BidID string `json:"bidid,omitempty"`
AccountID string `json:"account_id,omitempty"`
Bidder string `json:"bidder,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Type EventType `json:"type,omitempty"`
Format ResponseFormat `json:"format,omitempty"`
Analytics Analytics `json:"analytics,omitempty"`
BidID string `json:"bidid,omitempty"`
AccountID string `json:"account_id,omitempty"`
Bidder string `json:"bidder,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Integration string `json:"integration,omitempty"`
}
15 changes: 8 additions & 7 deletions config/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ const (

// Account represents a publisher account configuration
type Account struct {
ID string `mapstructure:"id" json:"id"`
Disabled bool `mapstructure:"disabled" json:"disabled"`
CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"`
CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"`
ID string `mapstructure:"id" json:"id"`
Disabled bool `mapstructure:"disabled" json:"disabled"`
CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"`
CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"`
DefaultIntegration string `mapstructure:"default_integration" json:"default_integration"`
}

// AccountCCPA represents account-specific CCPA configuration
Expand Down
42 changes: 38 additions & 4 deletions endpoints/events/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/url"
"strconv"
"time"
"unicode"

"github.com/julienschmidt/httprouter"
accountService "github.com/prebid/prebid-server/account"
Expand All @@ -26,12 +27,15 @@ const (
AccountIdParameter = "a"

// Optional
BidderParameter = "bidder"
TimestampParameter = "ts"
FormatParameter = "f"
AnalyticsParameter = "x"
BidderParameter = "bidder"
TimestampParameter = "ts"
FormatParameter = "f"
AnalyticsParameter = "x"
IntegrationTypeParameter = "int"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking, is this a standardized query parameter agreed upon with the PBS committee?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I believe so, I'll double check with Mansi, but I believe this is correct

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this is the name of the parameter in PBS-Java.

)

const integrationParamMaxLength = 64

type eventEndpoint struct {
Accounts stored_requests.AccountFetcher
Analytics analytics.PBSAnalyticsModule
Expand Down Expand Up @@ -162,6 +166,10 @@ func ParseEventRequest(r *http.Request) (*analytics.EventRequest, []error) {
errs = append(errs, err)
}

if err := readIntegrationType(event, r); err != nil {
errs = append(errs, err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add coverage for this line? You should be able to just add another test which is basically a copy of TestShouldReturnBadRequestWhenFormatValueIsInvalid.

}

// Bidder
event.Bidder = r.URL.Query().Get(BidderParameter)

Expand Down Expand Up @@ -225,6 +233,10 @@ func optionalParameters(request *analytics.EventRequest) string {
r.Add(AnalyticsParameter, string(analytics.Disabled))
}

if request.Integration != "" {
r.Add(IntegrationTypeParameter, request.Integration)
}

opt := r.Encode()

if opt != "" {
Expand Down Expand Up @@ -323,3 +335,25 @@ func checkRequiredParameter(httpRequest *http.Request, parameter string) (string

return t, nil
}

func readIntegrationType(er *analytics.EventRequest, httpRequest *http.Request) error {
integrationType := httpRequest.URL.Query().Get(IntegrationParameter)
err := validateIntegrationType(integrationType)
if err != nil {
return err
}
er.Integration = integrationType
return nil
}

func validateIntegrationType(integrationType string) error {
if len(integrationType) > integrationParamMaxLength {
return errors.New("integration type length is too long")
}
for _, char := range integrationType {
if !unicode.IsDigit(char) && !unicode.IsLetter(char) && char != '-' && char != '_' {
return errors.New("integration type can only contain numbers, letters and these characters '-', '_'")
}
}
return nil
}
112 changes: 100 additions & 12 deletions endpoints/events/event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import (
"context"
"encoding/base64"
"encoding/json"
"github.com/prebid/prebid-server/analytics"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/stored_requests"
"github.com/stretchr/testify/assert"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/prebid/prebid-server/analytics"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/stored_requests"
"github.com/stretchr/testify/assert"
)

// Mock Analytics Module
Expand Down Expand Up @@ -392,6 +394,41 @@ func TestShouldNotPassEventToAnalyticsReporterWhenAccountNotFound(t *testing.T)
assert.Equal(t, "Account 'testacc' doesn't support events", string(d))
}

func TestShouldReturnBadRequestWhenIntegrationValueIsInvalid(t *testing.T) {
// mock AccountsFetcher
mockAccountsFetcher := &mockAccountsFetcher{}

// mock PBS Analytics Module
mockAnalyticsModule := &eventsMockAnalyticsModule{
Fail: false,
}

// mock config
cfg := &config.Configuration{
AccountDefaults: config.Account{},
}

// prepare
reqData := ""

req := httptest.NewRequest("GET", "/event?t=win&b=bidId&f=b&ts=1000&x=1&a=accountId&bidder=bidder&int=Te$tIntegrationType", strings.NewReader(reqData))
recorder := httptest.NewRecorder()

e := NewEventEndpoint(cfg, mockAccountsFetcher, mockAnalyticsModule)

// execute
e(recorder, req, nil)

d, err := ioutil.ReadAll(recorder.Result().Body)
if err != nil {
t.Fatal(err)
}

// validate
assert.Equal(t, 400, recorder.Result().StatusCode, "Expected 400 on request with invalid integration type parameter")
assert.Equal(t, "invalid request: integration type can only contain numbers, letters and these characters '-', '_'\n", string(d))
}

func TestShouldNotPassEventToAnalyticsReporterWhenAccountEventNotEnabled(t *testing.T) {

// mock AccountsFetcher
Expand Down Expand Up @@ -587,15 +624,16 @@ func TestShouldParseEventCorrectly(t *testing.T) {
expected *analytics.EventRequest
}{
"one": {
req: httptest.NewRequest("GET", "/event?t=win&b=bidId&f=b&ts=1000&x=1&a=accountId&bidder=bidder", strings.NewReader("")),
req: httptest.NewRequest("GET", "/event?t=win&b=bidId&f=b&ts=1000&x=1&a=accountId&bidder=bidder&int=intType", strings.NewReader("")),
expected: &analytics.EventRequest{
Type: analytics.Win,
BidID: "bidId",
Timestamp: 1000,
Bidder: "bidder",
AccountID: "",
Format: analytics.Blank,
Analytics: analytics.Enabled,
Type: analytics.Win,
BidID: "bidId",
Timestamp: 1000,
Bidder: "bidder",
AccountID: "",
Format: analytics.Blank,
Analytics: analytics.Enabled,
Integration: "intType",
},
},
"two": {
Expand Down Expand Up @@ -652,6 +690,19 @@ func TestEventRequestToUrl(t *testing.T) {
},
want: "http://localhost:8000/event?t=win&b=bidid&a=accountId&bidder=bidder&f=i&ts=1234567&x=0",
},
"three": {
er: &analytics.EventRequest{
Type: analytics.Win,
BidID: "bidid",
AccountID: "accountId",
Bidder: "bidder",
Timestamp: 1234567,
Format: analytics.Image,
Analytics: analytics.Disabled,
Integration: "integration",
},
want: "http://localhost:8000/event?t=win&b=bidid&a=accountId&bidder=bidder&f=i&int=integration&ts=1234567&x=0",
},
}

for name, test := range tests {
Expand All @@ -662,3 +713,40 @@ func TestEventRequestToUrl(t *testing.T) {
})
}
}

func TestReadIntegrationType(t *testing.T) {
testCases := []struct {
description string
givenHttpRequest *http.Request
expectedIntegrationType string
expectedError error
}{
{
description: "Integration type in http request is valid, expect same integration time and no errors",
givenHttpRequest: httptest.NewRequest("GET", "/event?t=win&b=bidId&f=b&ts=1000&x=1&a=accountId&bidder=bidder&int=TestIntegrationType", strings.NewReader("")),
expectedIntegrationType: "TestIntegrationType",
expectedError: nil,
},
{
description: "Integration type in http request is too long, expect too long error",
givenHttpRequest: httptest.NewRequest("GET", "/event?t=win&b=bidId&f=b&ts=1000&x=1&a=accountId&bidder=bidder&int=TestIntegrationTypeTooLongTestIntegrationTypeTooLongTestIntegrationType", strings.NewReader("")),
expectedError: errors.New("integration type length is too long"),
},
{
description: "Integration type in http request contains invalid character, expect invalid character error",
givenHttpRequest: httptest.NewRequest("GET", "/event?t=win&b=bidId&f=b&ts=1000&x=1&a=accountId&bidder=bidder&int=Te$tIntegrationType", strings.NewReader("")),
expectedError: errors.New("integration type can only contain numbers, letters and these characters '-', '_'"),
},
}

for _, test := range testCases {
testEventRequest := &analytics.EventRequest{}
err := readIntegrationType(testEventRequest, test.givenHttpRequest)
if test.expectedError != nil {
assert.Equal(t, test.expectedError, err, test.description)
} else {
assert.Empty(t, err, test.description)
assert.Equalf(t, test.expectedIntegrationType, testEventRequest.Integration, test.description)
}
}
}
Loading