diff --git a/router/router.go b/router/router.go index 5d160687b79..e8eb12db734 100644 --- a/router/router.go +++ b/router/router.go @@ -220,9 +220,9 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R return nil, err } - syncersByBidder, err := usersync.BuildSyncers(cfg, bidderInfos) - if err != nil { - return nil, err + syncersByBidder, errs := usersync.BuildSyncers(cfg, bidderInfos) + if errsFiltered := filterSyncerErrors(errs); len(errsFiltered) > 0 { + return nil, errortypes.NewAggregateError("user sync", errsFiltered) } syncerKeys := make([]string, 0, len(syncersByBidder)) @@ -341,7 +341,7 @@ func applyBidderInfoConfigOverrides(bidderInfos config.BidderInfos, adaptersCfg // the new config. if adapterCfg.UserSyncURL != "" { if bidderInfo.Syncer == nil { - return fmt.Errorf("failed to apply legacy usersync_url setting for bidder %s, bidder does not define a user sync or is disabled", bidderName) + return fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder does not define a user sync", strings.ToLower(bidderName)) } endpointsCount := 0 @@ -355,17 +355,17 @@ func applyBidderInfoConfigOverrides(bidderInfos config.BidderInfos, adaptersCfg } if endpointsCount == 0 { - return fmt.Errorf("failed to apply legacy usersync_url setting for bidder %s, bidder does not define user sync endpoints", bidderName) + return fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder does not define user sync endpoints", strings.ToLower(bidderName)) } // if the bidder defines both an iframe and redirect endpoint, we can't be sure which config value to // override, and it wouldn't be both. this is a fatal configuration error. if endpointsCount > 1 { - return fmt.Errorf("failed to apply legacy usersync_url setting for bidder %s, bidder defines multiple user sync endpoints", bidderName) + return fmt.Errorf("adapters.%s.usersync_url cannot be applied, bidder defines multiple user sync endpoints", strings.ToLower(bidderName)) } // provide a warning that this compatibility layer is temporary - glog.Warningf("legacy usersync_url setting for bidder %s will be removed in a future version of Prebid Server. please update to the latest user sync config values", bidderName) + glog.Warningf("adapters.%s.usersync_url is deprecated and will be removed in a future version, please update to the latest user sync config values", strings.ToLower(bidderName)) } bidderInfos[bidderName] = bidderInfo @@ -374,6 +374,22 @@ func applyBidderInfoConfigOverrides(bidderInfos config.BidderInfos, adaptersCfg return nil } +func filterSyncerErrors(errs []error) []error { + var fatalErrors []error + + for _, err := range errs { + syncerBuildErr, ok := err.(usersync.SyncerBuildError) + if ok && syncerBuildErr.Err == usersync.ErrSyncerURLRequired { + bidderName := strings.ToLower(syncerBuildErr.Bidder) + glog.Warningf("bidder %s supports user syncing but has endpoints with no default url, no syncs will be performed with %s.", bidderName, bidderName) + } else { + fatalErrors = append(fatalErrors, err) + } + } + + return fatalErrors +} + // Fixes #648 // // These CORS options pose a security risk... but it's a calculated one. diff --git a/router/router_test.go b/router/router_test.go index ec7ab43e946..f3a167967ce 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -2,6 +2,7 @@ package router import ( "encoding/json" + "errors" "io/ioutil" "net/http" "net/http/httptest" @@ -9,7 +10,9 @@ import ( "testing" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/usersync" "github.com/stretchr/testify/assert" ) @@ -104,19 +107,19 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { description: "UserSyncURL Override Syncer Not Defined", givenBidderInfos: config.BidderInfos{"a": {}}, givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, - expectedError: "failed to apply legacy usersync_url setting for bidder a, bidder does not define a user sync or is disabled", + expectedError: "adapters.a.usersync_url cannot be applied, bidder does not define a user sync", }, { description: "UserSyncURL Override Syncer Endpoints Not Defined", givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{}}}, givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, - expectedError: "failed to apply legacy usersync_url setting for bidder a, bidder does not define user sync endpoints", + expectedError: "adapters.a.usersync_url cannot be applied, bidder does not define user sync endpoints", }, { description: "UserSyncURL Override Ambiguous", givenBidderInfos: config.BidderInfos{"a": {Syncer: &config.Syncer{IFrame: &config.SyncerEndpoint{URL: "originalIFrame"}, Redirect: &config.SyncerEndpoint{URL: "originalRedirect"}}}}, givenAdaptersCfg: map[string]config.Adapter{"a": {UserSyncURL: "override"}}, - expectedError: "failed to apply legacy usersync_url setting for bidder a, bidder defines multiple user sync endpoints", + expectedError: "adapters.a.usersync_url cannot be applied, bidder defines multiple user sync endpoints", }, } @@ -131,6 +134,50 @@ func TestApplyBidderInfoConfigOverrides(t *testing.T) { } } +func TestFilterSyncerErrors(t *testing.T) { + fatalErrorA := errors.New("errorA") + fatalErrorB := &errortypes.BadInput{Message: "errorB"} + buildErrorURLRequired := usersync.SyncerBuildError{Bidder: "anyBidder", SyncerKey: "anyKey", Err: usersync.ErrSyncerURLRequired} + buildErrorOther := usersync.SyncerBuildError{Bidder: "anyBidder", SyncerKey: "anyKey", Err: usersync.ErrSyncerKeyRequired} + + var testCases = []struct { + description string + givenErrors []error + expectedErrors []error + }{ + { + description: "Nil", + givenErrors: nil, + expectedErrors: nil, + }, + { + description: "Empty", + givenErrors: []error{}, + expectedErrors: nil, + }, + { + description: "One - Not Filtered", + givenErrors: []error{fatalErrorA}, + expectedErrors: []error{fatalErrorA}, + }, + { + description: "One - Filtered", + givenErrors: []error{buildErrorURLRequired}, + expectedErrors: nil, + }, + { + description: "Many - Mixed", + givenErrors: []error{fatalErrorA, fatalErrorB, buildErrorURLRequired, buildErrorOther}, + expectedErrors: []error{fatalErrorA, fatalErrorB, buildErrorOther}, + }, + } + + for _, test := range testCases { + filteredErrs := filterSyncerErrors(test.givenErrors) + assert.Equal(t, test.expectedErrors, filteredErrs, test.description) + } +} + // Prevents #648 func TestCORSSupport(t *testing.T) { const origin = "https://publisher-domain.com" diff --git a/static/bidder-info/adocean.yaml b/static/bidder-info/adocean.yaml index 6374d752c34..e53dcc32a5f 100644 --- a/static/bidder-info/adocean.yaml +++ b/static/bidder-info/adocean.yaml @@ -8,3 +8,8 @@ capabilities: site: mediaTypes: - banner +userSync: + redirect: + # adocean supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/adtarget.yaml b/static/bidder-info/adtarget.yaml index cc064b9ca6b..694ed139095 100644 --- a/static/bidder-info/adtarget.yaml +++ b/static/bidder-info/adtarget.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + iframe: + # adtarget supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/adtelligent.yaml b/static/bidder-info/adtelligent.yaml index 30f55ea912e..6cb2c514743 100644 --- a/static/bidder-info/adtelligent.yaml +++ b/static/bidder-info/adtelligent.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + iframe: + # adtelligent supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/adxcg.yaml b/static/bidder-info/adxcg.yaml index e5535ae7258..d2b15524310 100644 --- a/static/bidder-info/adxcg.yaml +++ b/static/bidder-info/adxcg.yaml @@ -11,3 +11,8 @@ capabilities: - banner - video - native +userSync: + redirect: + # adxcg supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/audienceNetwork.yaml b/static/bidder-info/audienceNetwork.yaml index 324e5c6dff8..451959f7ec5 100644 --- a/static/bidder-info/audienceNetwork.yaml +++ b/static/bidder-info/audienceNetwork.yaml @@ -6,3 +6,8 @@ capabilities: - banner - video - native +userSync: + redirect: + # facebook's audience network supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/bmtm.yaml b/static/bidder-info/bmtm.yaml index c13bf17db73..526e7d857af 100644 --- a/static/bidder-info/bmtm.yaml +++ b/static/bidder-info/bmtm.yaml @@ -6,3 +6,8 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + # bmtm supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml index bfa098ba39d..0898e5938ec 100644 --- a/static/bidder-info/criteo.yaml +++ b/static/bidder-info/criteo.yaml @@ -7,4 +7,9 @@ capabilities: - banner site: mediaTypes: - - banner \ No newline at end of file + - banner +userSync: + redirect: + # criteo supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/dmx.yaml b/static/bidder-info/dmx.yaml index d29d699daeb..04e3612512b 100644 --- a/static/bidder-info/dmx.yaml +++ b/static/bidder-info/dmx.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + # dmx supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/gamma.yaml b/static/bidder-info/gamma.yaml index aedaf2cf749..ef230dc30c0 100644 --- a/static/bidder-info/gamma.yaml +++ b/static/bidder-info/gamma.yaml @@ -5,3 +5,8 @@ capabilities: mediaTypes: - banner - video +userSync: + iframe: + # gamma supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/invibes.yaml b/static/bidder-info/invibes.yaml index 45184fb9f65..1da3c05ffbe 100644 --- a/static/bidder-info/invibes.yaml +++ b/static/bidder-info/invibes.yaml @@ -5,3 +5,8 @@ capabilities: site: mediaTypes: - banner +userSync: + iframe: + # invibes supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/lockerdome.yaml b/static/bidder-info/lockerdome.yaml index eebf3b87841..38e3e346be7 100644 --- a/static/bidder-info/lockerdome.yaml +++ b/static/bidder-info/lockerdome.yaml @@ -7,10 +7,12 @@ capabilities: site: mediaTypes: - banner -# lockerdome requires a platform id for their user sync process. replace <> -# in the url below and uncomment the userSync section to enable. -# -#userSync: -# redirect: -# url: "https://lockerdome.com/usync/prebidserver?pid=<>&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" -# userMacro: "{{uid}}" +userSync: + redirect: + # lockerdome supports user syncing, but requires a platform id to be configured by + # the host. remove the empty url, replace <> with the value provided, and + # uncomment to enable. + # + # url: "https://lockerdome.com/usync/prebidserver?pid=<>&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}" + url: "" + userMacro: "{{uid}}" diff --git a/static/bidder-info/mediafuse.yaml b/static/bidder-info/mediafuse.yaml index 98611402905..5e2cedc7932 100644 --- a/static/bidder-info/mediafuse.yaml +++ b/static/bidder-info/mediafuse.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + iframe: + # mediafuse supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index 12744fdc75e..58196af4c3f 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -5,3 +5,8 @@ capabilities: site: mediaTypes: - banner +userSync: + redirect: + # rtbhouse supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml index 0f19ddb9627..351918ca3cd 100644 --- a/static/bidder-info/rubicon.yaml +++ b/static/bidder-info/rubicon.yaml @@ -9,3 +9,8 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + # rubicon supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/smarthub.yaml b/static/bidder-info/smarthub.yaml index bfee9490840..32a7a1d0c40 100644 --- a/static/bidder-info/smarthub.yaml +++ b/static/bidder-info/smarthub.yaml @@ -11,3 +11,8 @@ capabilities: - banner - video - native +userSync: + redirect: + # smarthub supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/synacormedia.yaml b/static/bidder-info/synacormedia.yaml index 14a4bcd1244..774ba35720f 100644 --- a/static/bidder-info/synacormedia.yaml +++ b/static/bidder-info/synacormedia.yaml @@ -13,3 +13,8 @@ userSync: iframe: url: "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb={{.RedirectURL}}" userMacro: "[USER_ID]" +userSync: + iframe: + # synacormedia supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/verizonmedia.yaml b/static/bidder-info/verizonmedia.yaml index 0b4642fcb6a..f629c5f7bf5 100644 --- a/static/bidder-info/verizonmedia.yaml +++ b/static/bidder-info/verizonmedia.yaml @@ -8,3 +8,8 @@ capabilities: site: mediaTypes: - banner +userSync: + redirect: + # verizonmedia supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/viewdeos.yaml b/static/bidder-info/viewdeos.yaml index 9483e281de0..3ce7f9cf26a 100644 --- a/static/bidder-info/viewdeos.yaml +++ b/static/bidder-info/viewdeos.yaml @@ -10,3 +10,8 @@ capabilities: mediaTypes: - banner - video +userSync: + redirect: + # viewdeos supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/static/bidder-info/vrtcal.yaml b/static/bidder-info/vrtcal.yaml index fcf9294adcb..e86513f38e1 100644 --- a/static/bidder-info/vrtcal.yaml +++ b/static/bidder-info/vrtcal.yaml @@ -4,3 +4,8 @@ capabilities: app: mediaTypes: - banner +userSync: + redirect: + # vrtcal supports user syncing, but requires configuration by the host. paste the + # url provided by the bidder below to enable. + url: "" \ No newline at end of file diff --git a/usersync/syncer.go b/usersync/syncer.go index bd23e621eae..220a0b5fa68 100644 --- a/usersync/syncer.go +++ b/usersync/syncer.go @@ -58,19 +58,26 @@ const ( setuidSyncTypeRedirect = "i" // i = image response ) -var errEndpointRequired = errors.New("at least one endpoint (iframe and/or redirect) is required") -var errKeyRequired = errors.New("key is required") -var errDefaultSyncTypeRequired = errors.New("default sync type is required when more then one sync endpoint is configured") +var ErrSyncerEndpointRequired = errors.New("at least one endpoint (iframe and/or redirect) is required") +var ErrSyncerURLRequired = errors.New("each endpoint defined (iframe and/or redirect) must specify a url") +var ErrSyncerKeyRequired = errors.New("key is required") +var ErrSyncerDefaultSyncTypeRequired = errors.New("default sync type is required when more then one sync endpoint is configured") // NewSyncer creates a new Syncer from the provided configuration, or an error if macro substition // fails or an endpoint url is invalid. func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer) (Syncer, error) { if syncerConfig.Key == "" { - return nil, errKeyRequired + return nil, ErrSyncerKeyRequired } if syncerConfig.IFrame == nil && syncerConfig.Redirect == nil { - return nil, errEndpointRequired + return nil, ErrSyncerEndpointRequired + } + + // bidders may define an endpoint but leave the url empty as a way to communicate with the host + // that they support this endpoint, but don't provide a default value. + if (syncerConfig.IFrame != nil && syncerConfig.IFrame.URL == "") || (syncerConfig.Redirect != nil && syncerConfig.Redirect.URL == "") { + return nil, ErrSyncerURLRequired } syncer := standardSyncer{ @@ -112,7 +119,7 @@ func NewSyncer(hostConfig config.UserSync, syncerConfig config.Syncer) (Syncer, func resolveDefaultSyncType(syncerConfig config.Syncer) (SyncType, error) { if syncerConfig.Default == "" { if syncerConfig.IFrame != nil && syncerConfig.Redirect != nil { - return SyncTypeUnknown, errDefaultSyncTypeRequired + return SyncTypeUnknown, ErrSyncerDefaultSyncTypeRequired } else if syncerConfig.IFrame != nil { return SyncTypeIFrame, nil } else { diff --git a/usersync/syncer_test.go b/usersync/syncer_test.go index 2f4e3203905..05fc8506623 100644 --- a/usersync/syncer_test.go +++ b/usersync/syncer_test.go @@ -17,6 +17,7 @@ func TestNewSyncer(t *testing.T) { supportCORS = true hostConfig = config.UserSync{ExternalURL: "http://host.com", RedirectURL: "{{.ExternalURL}}/host"} macroValues = macros.UserSyncTemplateParams{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"} + emptyConfig = &config.SyncerEndpoint{} iframeConfig = &config.SyncerEndpoint{URL: "https://bidder.com/iframe?redirect={{.RedirectURL}}"} redirectConfig = &config.SyncerEndpoint{URL: "https://bidder.com/redirect?redirect={{.RedirectURL}}"} errParseConfig = &config.SyncerEndpoint{URL: "{{malformed}}"} @@ -50,6 +51,14 @@ func TestNewSyncer(t *testing.T) { givenRedirectConfig: nil, expectedError: "at least one endpoint (iframe and/or redirect) is required", }, + { + description: "Missing URLs - IFrame & Redirect", + givenKey: "a", + givenDefault: "redirect", + givenIFrameConfig: emptyConfig, + givenRedirectConfig: emptyConfig, + expectedError: "each endpoint defined (iframe and/or redirect) must specify a url", + }, { description: "Resolve Default Sync Type Error ", givenKey: "a", @@ -68,6 +77,14 @@ func TestNewSyncer(t *testing.T) { expectedIFrame: "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fhost", expectedRedirect: "https://bidder.com/redirect?redirect=http%3A%2F%2Fhost.com%2Fhost", }, + { + description: "IFrame - Missing URL Error", + givenKey: "a", + givenDefault: "iframe", + givenIFrameConfig: emptyConfig, + givenRedirectConfig: nil, + expectedError: "each endpoint defined (iframe and/or redirect) must specify a url", + }, { description: "IFrame - Parse Error", givenKey: "a", @@ -84,6 +101,14 @@ func TestNewSyncer(t *testing.T) { givenRedirectConfig: nil, expectedError: "iframe composed url: \"notAURL:http%3A%2F%2Fhost.com%2Fhost\" is invalid", }, + { + description: "Redirect - Missing URL Error", + givenKey: "a", + givenDefault: "redirect", + givenIFrameConfig: nil, + givenRedirectConfig: emptyConfig, + expectedError: "each endpoint defined (iframe and/or redirect) must specify a url", + }, { description: "Redirect - Parse Error", givenKey: "a", diff --git a/usersync/syncersbuilder.go b/usersync/syncersbuilder.go index bf89b487aa8..96b6b9d219e 100644 --- a/usersync/syncersbuilder.go +++ b/usersync/syncersbuilder.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/errortypes" ) type namedSyncerConfig struct { @@ -14,7 +13,19 @@ type namedSyncerConfig struct { cfg config.Syncer } -func BuildSyncers(hostConfig *config.Configuration, bidderInfos config.BidderInfos) (map[string]Syncer, error) { +// SyncerBuildError represents an error with building a syncer. +type SyncerBuildError struct { + Bidder string + SyncerKey string + Err error +} + +// Error implements the standard error interface. +func (e SyncerBuildError) Error() string { + return fmt.Sprintf("cannot create syncer for bidder %s with key %s: %v", e.Bidder, e.SyncerKey, e.Err) +} + +func BuildSyncers(hostConfig *config.Configuration, bidderInfos config.BidderInfos) (map[string]Syncer, []error) { // map syncer config by bidder cfgByBidder := make(map[string]config.Syncer, len(bidderInfos)) for bidder, cfg := range bidderInfos { @@ -50,7 +61,11 @@ func BuildSyncers(hostConfig *config.Configuration, bidderInfos config.BidderInf syncer, err := NewSyncer(hostUserSyncConfig, primaryCfg.cfg) if err != nil { - errs = append(errs, fmt.Errorf("cannot create syncer for bidder %s with key %s. %v", primaryCfg.name, key, err)) + errs = append(errs, SyncerBuildError{ + Bidder: primaryCfg.name, + SyncerKey: key, + Err: err, + }) continue } @@ -60,7 +75,7 @@ func BuildSyncers(hostConfig *config.Configuration, bidderInfos config.BidderInf } if len(errs) > 0 { - return nil, errortypes.NewAggregateError("user sync", errs) + return nil, errs } return syncers, nil } diff --git a/usersync/syncersbuilder_test.go b/usersync/syncersbuilder_test.go index 29f00b79621..f166c538524 100644 --- a/usersync/syncersbuilder_test.go +++ b/usersync/syncersbuilder_test.go @@ -1,7 +1,7 @@ package usersync import ( - "strings" + "errors" "testing" "github.com/prebid/prebid-server/config" @@ -9,6 +9,15 @@ import ( "github.com/stretchr/testify/assert" ) +func TestSyncerBuildError(t *testing.T) { + err := SyncerBuildError{ + Bidder: "anyBidder", + SyncerKey: "anyKey", + Err: errors.New("anyError"), + } + assert.Equal(t, err.Error(), "cannot create syncer for bidder anyBidder with key anyKey: anyError") +} + func TestBuildSyncers(t *testing.T) { var ( hostConfig = config.Configuration{ExternalURL: "http://host.com", UserSync: config.UserSync{RedirectURL: "{{.ExternalURL}}/{{.SyncerKey}}/host"}} @@ -26,12 +35,11 @@ func TestBuildSyncers(t *testing.T) { // in these tests. Look carefully at the end of the expected iframe urls to see the syncer key. testCases := []struct { - description string - givenConfig config.Configuration - givenBidderInfos config.BidderInfos - expectedIFramesURLs map[string]string - expectedErrorHeader string - expectedErrorSegments []string + description string + givenConfig config.Configuration + givenBidderInfos config.BidderInfos + expectedIFramesURLs map[string]string + expectedErrors []string }{ { description: "One", @@ -50,12 +58,11 @@ func TestBuildSyncers(t *testing.T) { }, }, { - description: "One - Syncer Error", - givenConfig: hostConfig, - givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAError}, - expectedErrorHeader: "user sync (1 error)", - expectedErrorSegments: []string{ - "cannot create syncer for bidder bidder1 with key a. default is set to redirect but no redirect endpoint is configured\n", + description: "One - Syncer Error", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAError}, + expectedErrors: []string{ + "cannot create syncer for bidder bidder1 with key a: default is set to redirect but no redirect endpoint is configured", }, }, { @@ -77,21 +84,27 @@ func TestBuildSyncers(t *testing.T) { }, }, { - description: "Many - Same Syncers - Many Primaries", - givenConfig: hostConfig, - givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated, "bidder2": infoKeyAPopulated}, - expectedErrorHeader: "user sync (1 error)", - expectedErrorSegments: []string{ - "bidders bidder1, bidder2 define endpoints (iframe and/or redirect) for the same syncer key, but only one bidder is permitted to define endpoints\n", + description: "Many - Same Syncers - Many Primaries - All Populated", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAPopulated, "bidder2": infoKeyAPopulated}, + expectedErrors: []string{ + "bidders bidder1, bidder2 define endpoints (iframe and/or redirect) for the same syncer key, but only one bidder is permitted to define endpoints", + }, + }, + { + description: "Many - Same Syncers - Many Primaries - None Populated", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAEmpty, "bidder2": infoKeyAEmpty}, + expectedErrors: []string{ + "bidders bidder1, bidder2 share the same syncer key, but none define endpoints (iframe and/or redirect)", }, }, { - description: "Many - Sync Error - Bidder Correct", - givenConfig: hostConfig, - givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAEmpty, "bidder2": infoKeyAError}, - expectedErrorHeader: "user sync (1 error)", - expectedErrorSegments: []string{ - "cannot create syncer for bidder bidder2 with key a. default is set to redirect but no redirect endpoint is configured\n", + description: "Many - Sync Error - Bidder Correct", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAEmpty, "bidder2": infoKeyAError}, + expectedErrors: []string{ + "cannot create syncer for bidder bidder2 with key a: default is set to redirect but no redirect endpoint is configured", }, }, { @@ -111,13 +124,12 @@ func TestBuildSyncers(t *testing.T) { }, }, { - description: "Many - Multiple Errors", - givenConfig: hostConfig, - givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAError, "bidder2": infoKeyBEmpty}, - expectedErrorHeader: "user sync (2 errors)", - expectedErrorSegments: []string{ - "cannot create syncer for bidder bidder1 with key a. default is set to redirect but no redirect endpoint is configured\n", - "cannot create syncer for bidder bidder2 with key b. at least one endpoint (iframe and/or redirect) is required\n", + description: "Many - Multiple Errors", + givenConfig: hostConfig, + givenBidderInfos: map[string]config.BidderInfo{"bidder1": infoKeyAError, "bidder2": infoKeyBEmpty}, + expectedErrors: []string{ + "cannot create syncer for bidder bidder1 with key a: default is set to redirect but no redirect endpoint is configured", + "cannot create syncer for bidder bidder2 with key b: at least one endpoint (iframe and/or redirect) is required", }, }, { @@ -131,10 +143,10 @@ func TestBuildSyncers(t *testing.T) { } for _, test := range testCases { - result, err := BuildSyncers(&test.givenConfig, test.givenBidderInfos) + result, errs := BuildSyncers(&test.givenConfig, test.givenBidderInfos) - if test.expectedErrorHeader == "" { - assert.NoError(t, err, test.description+":err") + if len(test.expectedErrors) == 0 { + assert.Empty(t, errs, test.description+":err") resultRenderedIFrameURLS := map[string]string{} for k, v := range result { iframeRendered, err := v.GetSync([]SyncType{SyncTypeIFrame}, privacy.Policies{}) @@ -144,11 +156,11 @@ func TestBuildSyncers(t *testing.T) { } assert.Equal(t, test.expectedIFramesURLs, resultRenderedIFrameURLS, test.description+":result") } else { - errMessage := err.Error() - assert.True(t, strings.HasPrefix(errMessage, test.expectedErrorHeader), test.description+":err") - for _, s := range test.expectedErrorSegments { - assert.Contains(t, errMessage, s, test.description+":err") + errMessages := make([]string, 0, len(errs)) + for _, e := range errs { + errMessages = append(errMessages, e.Error()) } + assert.ElementsMatch(t, test.expectedErrors, errMessages, test.description+":err") assert.Empty(t, result, test.description+":result") } }