Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions config/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ type Account struct {

// CookieSync represents the account-level defaults for the cookie sync endpoint.
type CookieSync struct {
DefaultLimit *int `mapstructure:"default_limit" json:"default_limit"`
MaxLimit *int `mapstructure:"max_limit" json:"max_limit"`
DefaultCoopSync *bool `mapstructure:"default_coop_sync" json:"default_coop_sync"`
PriorityGroups [][]string `mapstructure:"priority_groups" json:"priority_groups"`
DefaultLimit *int `mapstructure:"default_limit" json:"default_limit"`
MaxLimit *int `mapstructure:"max_limit" json:"max_limit"`
DefaultCoopSync *bool `mapstructure:"default_coop_sync" json:"default_coop_sync"`
PriorityGroups [][]string `mapstructure:"priority_groups" json:"priority_groups"`
PriorityGroupsOnly *bool `mapstructure:"priority_groups_only" json:"priority_groups_only"`
}

// AccountCCPA represents account-specific CCPA configuration
Expand Down
40 changes: 21 additions & 19 deletions endpoints/cookie_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,24 @@
"github.com/julienschmidt/httprouter"
gpplib "github.com/prebid/go-gpp"
gppConstants "github.com/prebid/go-gpp/constants"
accountService "github.com/prebid/prebid-server/v3/account"
"github.com/prebid/prebid-server/v3/analytics"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/errortypes"
"github.com/prebid/prebid-server/v3/gdpr"
"github.com/prebid/prebid-server/v3/logger"
"github.com/prebid/prebid-server/v3/macros"
"github.com/prebid/prebid-server/v3/metrics"
"github.com/prebid/prebid-server/v3/openrtb_ext"
"github.com/prebid/prebid-server/v3/privacy"
"github.com/prebid/prebid-server/v3/privacy/ccpa"
gppPrivacy "github.com/prebid/prebid-server/v3/privacy/gpp"
"github.com/prebid/prebid-server/v3/stored_requests"
"github.com/prebid/prebid-server/v3/usersync"
"github.com/prebid/prebid-server/v3/util/jsonutil"
stringutil "github.com/prebid/prebid-server/v3/util/stringutil"
"github.com/prebid/prebid-server/v3/util/timeutil"
accountService "github.com/prebid/prebid-server/v4/account"

Check failure on line 18 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/account; to add it:
"github.com/prebid/prebid-server/v4/analytics"

Check failure on line 19 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/analytics; to add it:
"github.com/prebid/prebid-server/v4/config"

Check failure on line 20 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/config; to add it:
"github.com/prebid/prebid-server/v4/errortypes"

Check failure on line 21 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/errortypes; to add it:
"github.com/prebid/prebid-server/v4/gdpr"

Check failure on line 22 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/gdpr; to add it:
"github.com/prebid/prebid-server/v4/logger"

Check failure on line 23 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/logger; to add it:
"github.com/prebid/prebid-server/v4/macros"

Check failure on line 24 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/macros; to add it:
"github.com/prebid/prebid-server/v4/metrics"

Check failure on line 25 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/metrics; to add it:
"github.com/prebid/prebid-server/v4/openrtb_ext"

Check failure on line 26 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/openrtb_ext; to add it:
"github.com/prebid/prebid-server/v4/privacy"

Check failure on line 27 in endpoints/cookie_sync.go

View workflow job for this annotation

GitHub Actions / Test and Validate

no required module provides package github.com/prebid/prebid-server/v4/privacy; to add it:
"github.com/prebid/prebid-server/v4/privacy/ccpa"
gppPrivacy "github.com/prebid/prebid-server/v4/privacy/gpp"
"github.com/prebid/prebid-server/v4/stored_requests"
"github.com/prebid/prebid-server/v4/usersync"
"github.com/prebid/prebid-server/v4/util/jsonutil"
"github.com/prebid/prebid-server/v4/util/ptrutil"
stringutil "github.com/prebid/prebid-server/v4/util/stringutil"
"github.com/prebid/prebid-server/v4/util/timeutil"
)

const receiveCookieDeprecation = "receive-cookie-deprecation"
Expand Down Expand Up @@ -183,8 +184,9 @@
rx := usersync.Request{
Bidders: request.Bidders,
Cooperative: usersync.Cooperative{
Enabled: (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.UserSync.Cooperative.EnabledByDefault),
PriorityGroups: c.findPriorityGroups(account.CookieSync),
Enabled: (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.UserSync.Cooperative.EnabledByDefault),
PriorityGroups: c.findPriorityGroups(account.CookieSync),
PriorityGroupsOnly: ptrutil.ValueOrDefault(account.CookieSync.PriorityGroupsOnly),
},
Debug: request.Debug,
Limit: limit,
Expand Down
56 changes: 46 additions & 10 deletions endpoints/cookie_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2603,7 +2603,7 @@ func TestCookieSyncFindPriorityGroups(t *testing.T) {
}

// createAccountJSON creates a JSON representation of account config for testing
func createAccountJSON(priorityGroups [][]string, defaultCoopSync *bool) json.RawMessage {
func createAccountJSON(priorityGroups [][]string, defaultCoopSync *bool, priorityGroupsOnly *bool) json.RawMessage {
account := map[string]interface{}{
"cookie_sync": map[string]interface{}{
"priority_groups": priorityGroups,
Expand All @@ -2614,6 +2614,10 @@ func createAccountJSON(priorityGroups [][]string, defaultCoopSync *bool) json.Ra
account["cookie_sync"].(map[string]interface{})["default_coop_sync"] = *defaultCoopSync
}

if priorityGroupsOnly != nil {
account["cookie_sync"].(map[string]interface{})["priority_groups_only"] = *priorityGroupsOnly
}

jsonData, _ := json.Marshal(account)
return json.RawMessage(jsonData)
}
Expand Down Expand Up @@ -2652,13 +2656,15 @@ func TestCookieSyncPriorityGroupsIntegration(t *testing.T) {
}

testCases := []struct {
description string
givenRequestBody string
givenAccountPriorityGroups [][]string
givenAccountDefaultCoopSync *bool
givenGlobalPriorityGroups [][]string
givenCooperativeEnabledByDef bool
shouldContainBidders []string
description string
givenRequestBody string
givenAccountPriorityGroups [][]string
givenAccountDefaultCoopSync *bool
givenAccountPriorityGroupsOnly *bool
givenGlobalPriorityGroups [][]string
givenCooperativeEnabledByDef bool
shouldContainBidders []string
shouldNotContainBidders []string
}{
{
description: "Account-level priority groups used with cooperative sync enabled",
Expand Down Expand Up @@ -2713,6 +2719,31 @@ func TestCookieSyncPriorityGroupsIntegration(t *testing.T) {
givenCooperativeEnabledByDef: false,
shouldContainBidders: []string{"appnexus", "rubicon", "pubmatic"},
},
{
description: "Priority groups only — remaining bidders excluded",
givenRequestBody: `{"bidders":["appnexus"], "coopSync": true, "limit": 10, "account": "test_account"}`,
givenAccountPriorityGroups: [][]string{
{"rubicon"},
},
givenAccountDefaultCoopSync: ptrutil.ToPtr(true),
givenAccountPriorityGroupsOnly: ptrutil.ToPtr(true),
givenGlobalPriorityGroups: [][]string{{"ignored"}},
givenCooperativeEnabledByDef: false,
shouldContainBidders: []string{"appnexus", "rubicon"},
shouldNotContainBidders: []string{"pubmatic"},
},
{
description: "Priority groups only false — remaining bidders included",
givenRequestBody: `{"bidders":["appnexus"], "coopSync": true, "limit": 10, "account": "test_account"}`,
givenAccountPriorityGroups: [][]string{
{"rubicon"},
},
givenAccountDefaultCoopSync: ptrutil.ToPtr(true),
givenAccountPriorityGroupsOnly: ptrutil.ToPtr(false),
givenGlobalPriorityGroups: [][]string{{"ignored"}},
givenCooperativeEnabledByDef: false,
shouldContainBidders: []string{"appnexus", "rubicon", "pubmatic"},
},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -2750,7 +2781,7 @@ func TestCookieSyncPriorityGroupsIntegration(t *testing.T) {
&mockAnalytics,
&FakeAccountsFetcher{
AccountData: map[string]json.RawMessage{
"test_account": createAccountJSON(tc.givenAccountPriorityGroups, tc.givenAccountDefaultCoopSync),
"test_account": createAccountJSON(tc.givenAccountPriorityGroups, tc.givenAccountDefaultCoopSync, tc.givenAccountPriorityGroupsOnly),
},
},
bidders,
Expand Down Expand Up @@ -2780,6 +2811,11 @@ func TestCookieSyncPriorityGroupsIntegration(t *testing.T) {
for _, expected := range tc.shouldContainBidders {
assert.Contains(t, actualBidders, expected, "Expected bidder %s to be present in response", expected)
}

// Check that excluded bidders are not present
for _, excluded := range tc.shouldNotContainBidders {
assert.NotContains(t, actualBidders, excluded, "Bidder %s should not be present in response", excluded)
}
})
}
}
Expand Down Expand Up @@ -2923,7 +2959,7 @@ func TestCookieSyncPriorityGroupsEdgeCases(t *testing.T) {
&mockAnalytics,
&FakeAccountsFetcher{
AccountData: map[string]json.RawMessage{
"test_account": createAccountJSON(tc.givenAccountPriorityGroups, tc.givenAccountDefaultCoopSync),
"test_account": createAccountJSON(tc.givenAccountPriorityGroups, tc.givenAccountDefaultCoopSync, nil),
},
},
bidders,
Expand Down
10 changes: 6 additions & 4 deletions usersync/bidderchooser.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type standardBidderChooser struct {

func (c standardBidderChooser) choose(requested, available []string, cooperative Cooperative) []string {
if cooperative.Enabled {
return c.chooseCooperative(requested, available, cooperative.PriorityGroups)
return c.chooseCooperative(requested, available, cooperative)
}

if len(requested) == 0 {
Expand All @@ -23,7 +23,7 @@ func (c standardBidderChooser) choose(requested, available []string, cooperative
return c.shuffledCopy(requested)
}

func (c standardBidderChooser) chooseCooperative(requested, available []string, priorityGroups [][]string) []string {
func (c standardBidderChooser) chooseCooperative(requested, available []string, cooperative Cooperative) []string {
// allocate enough memory for the slice to try to avoid re-allocation. the 50% overhead is a guess
// at a satisfactory value. since all available bidders are included in the slice, along with
// requested and prioritized bidders, expect there to be be many duplicates. the duplicate are
Expand All @@ -35,12 +35,14 @@ func (c standardBidderChooser) chooseCooperative(requested, available []string,
bidders = c.shuffledAppend(bidders, requested)

// priority groups
for _, group := range priorityGroups {
for _, group := range cooperative.PriorityGroups {
bidders = c.shuffledAppend(bidders, group)
}

// available
bidders = c.shuffledAppend(bidders, available)
if !cooperative.PriorityGroupsOnly {
bidders = c.shuffledAppend(bidders, available)
}

return bidders
}
Expand Down
95 changes: 55 additions & 40 deletions usersync/bidderchooser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ func TestBidderChooserChoose(t *testing.T) {
givenCooperative: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}}},
expected: []string{"r2", "r1", "pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"},
},
{
description: "Coop - PriorityGroupsOnly",
givenRequested: []string{"r1", "r2"},
givenCooperative: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}}, PriorityGroupsOnly: true},
expected: []string{"r2", "r1", "pr1B", "pr1A"},
},
}

for _, test := range testCases {
Expand All @@ -76,55 +82,64 @@ func TestBidderChooserCooperative(t *testing.T) {
shuffler := reverseShuffler{}
available := []string{"a1", "a2"}

testCases := []struct {
description string
givenRequested []string
givenPriorityGroups [][]string
expected []string
testCases := map[string]struct {
givenRequested []string
givenCoop Cooperative
expected []string
}{
{
description: "Nil",
givenRequested: nil,
givenPriorityGroups: nil,
expected: []string{"a2", "a1"},
"nil": {
givenRequested: nil,
givenCoop: Cooperative{Enabled: true},
expected: []string{"a2", "a1"},
},
{
description: "Empty",
givenRequested: []string{},
givenPriorityGroups: [][]string{},
expected: []string{"a2", "a1"},
"empty": {
givenRequested: []string{},
givenCoop: Cooperative{Enabled: true, PriorityGroups: [][]string{}},
expected: []string{"a2", "a1"},
},
{
description: "Requested",
givenRequested: []string{"r1", "r2"},
givenPriorityGroups: nil,
expected: []string{"r2", "r1", "a2", "a1"},
"requested": {
givenRequested: []string{"r1", "r2"},
givenCoop: Cooperative{Enabled: true},
expected: []string{"r2", "r1", "a2", "a1"},
},
{
description: "Priority Groups - One",
givenRequested: nil,
givenPriorityGroups: [][]string{{"pr1A", "pr1B"}},
expected: []string{"pr1B", "pr1A", "a2", "a1"},
"priority_groups_one": {
givenRequested: nil,
givenCoop: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}}},
expected: []string{"pr1B", "pr1A", "a2", "a1"},
},
{
description: "Priority Groups - Many",
givenRequested: nil,
givenPriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}},
expected: []string{"pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"},
"priority_groups_many": {
givenRequested: nil,
givenCoop: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}}},
expected: []string{"pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"},
},
{
description: "Requested + Priority Groups",
givenRequested: []string{"r1", "r2"},
givenPriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}},
expected: []string{"r2", "r1", "pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"},
"requested_and_priority_groups": {
givenRequested: []string{"r1", "r2"},
givenCoop: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}}},
expected: []string{"r2", "r1", "pr1B", "pr1A", "pr2B", "pr2A", "a2", "a1"},
},
"priority_groups_only_no_remaining": {
givenRequested: []string{"r1", "r2"},
givenCoop: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}}, PriorityGroupsOnly: true},
expected: []string{"r2", "r1", "pr1B", "pr1A"},
},
"priority_groups_only_no_requested": {
givenRequested: nil,
givenCoop: Cooperative{Enabled: true, PriorityGroups: [][]string{{"pr1A", "pr1B"}, {"pr2A", "pr2B"}}, PriorityGroupsOnly: true},
expected: []string{"pr1B", "pr1A", "pr2B", "pr2A"},
},
"priority_groups_only_empty_groups": {
givenRequested: []string{"r1", "r2"},
givenCoop: Cooperative{Enabled: true, PriorityGroups: [][]string{}, PriorityGroupsOnly: true},
expected: []string{"r2", "r1"},
},
}

for _, test := range testCases {
chooser := standardBidderChooser{shuffler: shuffler}
result := chooser.chooseCooperative(test.givenRequested, available, test.givenPriorityGroups)

assert.Equal(t, test.expected, result, test.description)
for name, test := range testCases {
t.Run(name, func(t *testing.T) {
chooser := standardBidderChooser{shuffler: shuffler}
result := chooser.chooseCooperative(test.givenRequested, available, test.givenCoop)
assert.Equal(t, test.expected, result)
})
}
}

Expand Down
5 changes: 3 additions & 2 deletions usersync/chooser.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ type Request struct {
// Cooperative specifies the settings for cooperative syncing for a given request, where bidders
// other than those used by the publisher are considered for syncing.
type Cooperative struct {
Enabled bool
PriorityGroups [][]string
Enabled bool
PriorityGroups [][]string
PriorityGroupsOnly bool
}

// Result specifies which bidders were included in the evaluation and which syncers were chosen.
Expand Down
Loading