github.com/prebid/prebid-server@v0.275.0/account/account_test.go (about)

     1  package account
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/buger/jsonparser"
    10  	"github.com/prebid/prebid-server/config"
    11  	"github.com/prebid/prebid-server/errortypes"
    12  	"github.com/prebid/prebid-server/metrics"
    13  	"github.com/prebid/prebid-server/openrtb_ext"
    14  	"github.com/prebid/prebid-server/stored_requests"
    15  	"github.com/prebid/prebid-server/util/iputil"
    16  	"github.com/prebid/prebid-server/util/ptrutil"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/mock"
    19  )
    20  
    21  var mockAccountData = map[string]json.RawMessage{
    22  	"valid_acct":                                   json.RawMessage(`{"disabled":false}`),
    23  	"invalid_acct_ipv6_ipv4":                       json.RawMessage(`{"disabled":false, "privacy": {"ipv6": {"anon_keep_bits": -32}, "ipv4": {"anon_keep_bits": -16}}}`),
    24  	"disabled_acct":                                json.RawMessage(`{"disabled":true}`),
    25  	"malformed_acct":                               json.RawMessage(`{"disabled":"invalid type"}`),
    26  	"gdpr_channel_enabled_acct":                    json.RawMessage(`{"disabled":false,"gdpr":{"channel_enabled":{"amp":true}}}`),
    27  	"ccpa_channel_enabled_acct":                    json.RawMessage(`{"disabled":false,"ccpa":{"channel_enabled":{"amp":true}}}`),
    28  	"gdpr_channel_enabled_deprecated_purpose_acct": json.RawMessage(`{"disabled":false,"gdpr":{"purpose1":{"enforce_purpose":"full"}, "channel_enabled":{"amp":true}}}`),
    29  	"gdpr_deprecated_purpose1":                     json.RawMessage(`{"disabled":false,"gdpr":{"purpose1":{"enforce_purpose":"full"}}}`),
    30  	"gdpr_deprecated_purpose2":                     json.RawMessage(`{"disabled":false,"gdpr":{"purpose2":{"enforce_purpose":"full"}}}`),
    31  	"gdpr_deprecated_purpose3":                     json.RawMessage(`{"disabled":false,"gdpr":{"purpose3":{"enforce_purpose":"full"}}}`),
    32  	"gdpr_deprecated_purpose4":                     json.RawMessage(`{"disabled":false,"gdpr":{"purpose4":{"enforce_purpose":"full"}}}`),
    33  	"gdpr_deprecated_purpose5":                     json.RawMessage(`{"disabled":false,"gdpr":{"purpose5":{"enforce_purpose":"full"}}}`),
    34  	"gdpr_deprecated_purpose6":                     json.RawMessage(`{"disabled":false,"gdpr":{"purpose6":{"enforce_purpose":"full"}}}`),
    35  	"gdpr_deprecated_purpose7":                     json.RawMessage(`{"disabled":false,"gdpr":{"purpose7":{"enforce_purpose":"full"}}}`),
    36  	"gdpr_deprecated_purpose8":                     json.RawMessage(`{"disabled":false,"gdpr":{"purpose8":{"enforce_purpose":"full"}}}`),
    37  	"gdpr_deprecated_purpose9":                     json.RawMessage(`{"disabled":false,"gdpr":{"purpose9":{"enforce_purpose":"full"}}}`),
    38  	"gdpr_deprecated_purpose10":                    json.RawMessage(`{"disabled":false,"gdpr":{"purpose10":{"enforce_purpose":"full"}}}`),
    39  }
    40  
    41  type mockAccountFetcher struct {
    42  }
    43  
    44  func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountDefaultsJSON json.RawMessage, accountID string) (json.RawMessage, []error) {
    45  	if account, ok := mockAccountData[accountID]; ok {
    46  		return account, nil
    47  	}
    48  	return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}}
    49  }
    50  
    51  func TestGetAccount(t *testing.T) {
    52  	unknown := metrics.PublisherUnknown
    53  	testCases := []struct {
    54  		accountID string
    55  		// account_required
    56  		required bool
    57  		// account_defaults.disabled
    58  		disabled bool
    59  		// checkDefaultIP indicates IPv6 and IPv6 should be set to default values
    60  		checkDefaultIP bool
    61  		// expected error, or nil if account should be found
    62  		err error
    63  	}{
    64  		// Blacklisted account is always rejected even in permissive setup
    65  		{accountID: "bad_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}},
    66  
    67  		// empty pubID
    68  		{accountID: unknown, required: false, disabled: false, err: nil},
    69  		{accountID: unknown, required: true, disabled: false, err: &errortypes.AcctRequired{}},
    70  		{accountID: unknown, required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
    71  		{accountID: unknown, required: true, disabled: true, err: &errortypes.AcctRequired{}},
    72  
    73  		// pubID given but is not a valid host account (does not exist)
    74  		{accountID: "doesnt_exist_acct", required: false, disabled: false, err: nil},
    75  		{accountID: "doesnt_exist_acct", required: true, disabled: false, err: nil},
    76  		{accountID: "doesnt_exist_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
    77  		{accountID: "doesnt_exist_acct", required: true, disabled: true, err: &errortypes.AcctRequired{}},
    78  
    79  		// pubID given and matches a valid host account with Disabled: false
    80  		{accountID: "valid_acct", required: false, disabled: false, err: nil},
    81  		{accountID: "valid_acct", required: true, disabled: false, err: nil},
    82  		{accountID: "valid_acct", required: false, disabled: true, err: nil},
    83  		{accountID: "valid_acct", required: true, disabled: true, err: nil},
    84  
    85  		{accountID: "invalid_acct_ipv6_ipv4", required: true, disabled: false, err: nil, checkDefaultIP: true},
    86  
    87  		// pubID given and matches a host account explicitly disabled (Disabled: true on account json)
    88  		{accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}},
    89  		{accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}},
    90  		{accountID: "disabled_acct", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
    91  		{accountID: "disabled_acct", required: true, disabled: true, err: &errortypes.BlacklistedAcct{}},
    92  
    93  		// pubID given and matches a host account with Disabled: false and GDPR purpose data to convert
    94  		{accountID: "gdpr_deprecated_purpose1", required: false, disabled: false, err: nil},
    95  		{accountID: "gdpr_deprecated_purpose1", required: true, disabled: false, err: nil},
    96  		{accountID: "gdpr_deprecated_purpose1", required: false, disabled: true, err: nil},
    97  		{accountID: "gdpr_deprecated_purpose1", required: true, disabled: true, err: nil},
    98  
    99  		// pubID given and matches a host account that has a malformed config
   100  		{accountID: "malformed_acct", required: false, disabled: false, err: &errortypes.MalformedAcct{}},
   101  		{accountID: "malformed_acct", required: true, disabled: false, err: &errortypes.MalformedAcct{}},
   102  		{accountID: "malformed_acct", required: false, disabled: true, err: &errortypes.MalformedAcct{}},
   103  		{accountID: "malformed_acct", required: true, disabled: true, err: &errortypes.MalformedAcct{}},
   104  
   105  		// account not provided (does not exist)
   106  		{accountID: "", required: false, disabled: false, err: nil},
   107  		{accountID: "", required: true, disabled: false, err: nil},
   108  		{accountID: "", required: false, disabled: true, err: &errortypes.BlacklistedAcct{}},
   109  		{accountID: "", required: true, disabled: true, err: &errortypes.AcctRequired{}},
   110  	}
   111  
   112  	for _, test := range testCases {
   113  		description := fmt.Sprintf(`ID=%s/required=%t/disabled=%t`, test.accountID, test.required, test.disabled)
   114  		t.Run(description, func(t *testing.T) {
   115  			cfg := &config.Configuration{
   116  				BlacklistedAcctMap: map[string]bool{"bad_acct": true},
   117  				AccountRequired:    test.required,
   118  				AccountDefaults:    config.Account{Disabled: test.disabled},
   119  			}
   120  			fetcher := &mockAccountFetcher{}
   121  			assert.NoError(t, cfg.MarshalAccountDefaults())
   122  
   123  			metrics := &metrics.MetricsEngineMock{}
   124  			metrics.Mock.On("RecordAccountGDPRPurposeWarning", mock.Anything, mock.Anything).Return()
   125  			metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return()
   126  
   127  			account, errors := GetAccount(context.Background(), cfg, fetcher, test.accountID, metrics)
   128  
   129  			if test.err == nil {
   130  				assert.Empty(t, errors)
   131  				assert.Equal(t, test.accountID, account.ID, "account.ID must match requested ID")
   132  				assert.Equal(t, false, account.Disabled, "returned account must not be disabled")
   133  			} else {
   134  				assert.NotEmpty(t, errors, "expected errors but got success")
   135  				assert.Nil(t, account, "return account must be nil on error")
   136  				assert.IsType(t, test.err, errors[0], "error is of unexpected type")
   137  			}
   138  			if test.checkDefaultIP {
   139  				assert.Equal(t, account.Privacy.IPv6Config.AnonKeepBits, iputil.IPv6DefaultMaskingBitSize, "ipv6 should be set to default value")
   140  				assert.Equal(t, account.Privacy.IPv4Config.AnonKeepBits, iputil.IPv4DefaultMaskingBitSize, "ipv4 should be set to default value")
   141  			}
   142  		})
   143  	}
   144  }
   145  
   146  func TestSetDerivedConfig(t *testing.T) {
   147  	tests := []struct {
   148  		description              string
   149  		purpose1VendorExceptions []openrtb_ext.BidderName
   150  		feature1VendorExceptions []openrtb_ext.BidderName
   151  		basicEnforcementVendors  []string
   152  		enforceAlgo              string
   153  		wantEnforceAlgoID        config.TCF2EnforcementAlgo
   154  	}{
   155  		{
   156  			description:              "Nil purpose 1 vendor exceptions",
   157  			purpose1VendorExceptions: nil,
   158  		},
   159  		{
   160  			description:              "One purpose 1 vendor exception",
   161  			purpose1VendorExceptions: []openrtb_ext.BidderName{"appnexus"},
   162  		},
   163  		{
   164  			description:              "Multiple purpose 1 vendor exceptions",
   165  			purpose1VendorExceptions: []openrtb_ext.BidderName{"appnexus", "rubicon"},
   166  		},
   167  		{
   168  			description:              "Nil feature 1 vendor exceptions",
   169  			feature1VendorExceptions: nil,
   170  		},
   171  		{
   172  			description:              "One feature 1 vendor exception",
   173  			feature1VendorExceptions: []openrtb_ext.BidderName{"appnexus"},
   174  		},
   175  		{
   176  			description:              "Multiple feature 1 vendor exceptions",
   177  			feature1VendorExceptions: []openrtb_ext.BidderName{"appnexus", "rubicon"},
   178  		},
   179  		{
   180  			description:             "Nil basic enforcement vendors",
   181  			basicEnforcementVendors: nil,
   182  		},
   183  		{
   184  			description:             "One basic enforcement vendor",
   185  			basicEnforcementVendors: []string{"appnexus"},
   186  		},
   187  		{
   188  			description:             "Multiple basic enforcement vendors",
   189  			basicEnforcementVendors: []string{"appnexus", "rubicon"},
   190  		},
   191  		{
   192  			description:       "Basic Enforcement algorithm",
   193  			enforceAlgo:       config.TCF2EnforceAlgoBasic,
   194  			wantEnforceAlgoID: config.TCF2BasicEnforcement,
   195  		},
   196  		{
   197  			description:       "Full Enforcement algorithm",
   198  			enforceAlgo:       config.TCF2EnforceAlgoFull,
   199  			wantEnforceAlgoID: config.TCF2FullEnforcement,
   200  		},
   201  	}
   202  
   203  	for _, tt := range tests {
   204  		account := config.Account{
   205  			GDPR: config.AccountGDPR{
   206  				Purpose1: config.AccountGDPRPurpose{
   207  					VendorExceptions: tt.purpose1VendorExceptions,
   208  					EnforceAlgo:      tt.enforceAlgo,
   209  				},
   210  				SpecialFeature1: config.AccountGDPRSpecialFeature{
   211  					VendorExceptions: tt.feature1VendorExceptions,
   212  				},
   213  				BasicEnforcementVendors: tt.basicEnforcementVendors,
   214  			},
   215  		}
   216  
   217  		setDerivedConfig(&account)
   218  
   219  		purpose1ExceptionMapKeys := make([]openrtb_ext.BidderName, 0)
   220  		for k := range account.GDPR.Purpose1.VendorExceptionMap {
   221  			purpose1ExceptionMapKeys = append(purpose1ExceptionMapKeys, k)
   222  		}
   223  
   224  		feature1ExceptionMapKeys := make([]openrtb_ext.BidderName, 0)
   225  		for k := range account.GDPR.SpecialFeature1.VendorExceptionMap {
   226  			feature1ExceptionMapKeys = append(feature1ExceptionMapKeys, k)
   227  		}
   228  
   229  		basicEnforcementMapKeys := make([]string, 0)
   230  		for k := range account.GDPR.BasicEnforcementVendorsMap {
   231  			basicEnforcementMapKeys = append(basicEnforcementMapKeys, k)
   232  		}
   233  
   234  		assert.ElementsMatch(t, purpose1ExceptionMapKeys, tt.purpose1VendorExceptions, tt.description)
   235  		assert.ElementsMatch(t, feature1ExceptionMapKeys, tt.feature1VendorExceptions, tt.description)
   236  		assert.ElementsMatch(t, basicEnforcementMapKeys, tt.basicEnforcementVendors, tt.description)
   237  
   238  		assert.Equal(t, account.GDPR.Purpose1.EnforceAlgoID, tt.wantEnforceAlgoID, tt.description)
   239  	}
   240  }
   241  
   242  func TestConvertGDPREnforcePurposeFields(t *testing.T) {
   243  	enforcePurposeNo := `{"enforce_purpose":"no"}`
   244  	enforcePurposeNoMapped := `{"enforce_algo":"full", "enforce_purpose":false}`
   245  	enforcePurposeFull := `{"enforce_purpose":"full"}`
   246  	enforcePurposeFullMapped := `{"enforce_algo":"full", "enforce_purpose":true}`
   247  
   248  	tests := []struct {
   249  		description       string
   250  		giveConfig        []byte
   251  		wantConfig        []byte
   252  		wantErr           error
   253  		wantPurposeFields []string
   254  	}{
   255  		{
   256  			description:       "config is nil",
   257  			giveConfig:        nil,
   258  			wantConfig:        nil,
   259  			wantErr:           nil,
   260  			wantPurposeFields: nil,
   261  		},
   262  		{
   263  			description:       "config is empty - no gdpr key",
   264  			giveConfig:        []byte(``),
   265  			wantConfig:        []byte(``),
   266  			wantErr:           nil,
   267  			wantPurposeFields: nil,
   268  		},
   269  		{
   270  			description:       "gdpr present but empty",
   271  			giveConfig:        []byte(`{"gdpr": {}}`),
   272  			wantConfig:        []byte(`{"gdpr": {}}`),
   273  			wantErr:           nil,
   274  			wantPurposeFields: nil,
   275  		},
   276  		{
   277  			description:       "gdpr present but invalid",
   278  			giveConfig:        []byte(`{"gdpr": {`),
   279  			wantConfig:        nil,
   280  			wantErr:           jsonparser.MalformedJsonError,
   281  			wantPurposeFields: nil,
   282  		},
   283  		{
   284  			description:       "gdpr.purpose1 present but empty",
   285  			giveConfig:        []byte(`{"gdpr":{"purpose1":{}}}`),
   286  			wantConfig:        []byte(`{"gdpr":{"purpose1":{}}}`),
   287  			wantErr:           nil,
   288  			wantPurposeFields: nil,
   289  		},
   290  		{
   291  			description:       "gdpr.purpose1.enforce_purpose is set to bool",
   292  			giveConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_purpose":true}}}`),
   293  			wantConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_purpose":true}}}`),
   294  			wantErr:           nil,
   295  			wantPurposeFields: nil,
   296  		},
   297  		{
   298  			description:       "gdpr.purpose1.enforce_purpose is set to string full",
   299  			giveConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"full"}}}`),
   300  			wantConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":true}}}`),
   301  			wantErr:           nil,
   302  			wantPurposeFields: []string{"purpose1"},
   303  		},
   304  		{
   305  			description:       "gdpr.purpose1.enforce_purpose is set to string no",
   306  			giveConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"no"}}}`),
   307  			wantConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":false}}}`),
   308  			wantErr:           nil,
   309  			wantPurposeFields: []string{"purpose1"},
   310  		},
   311  		{
   312  			description:       "gdpr.purpose1.enforce_purpose is set to string no and other fields are untouched during conversion",
   313  			giveConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_purpose":"no", "enforce_vendors":true}}}`),
   314  			wantConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":false, "enforce_vendors":true}}}`),
   315  			wantErr:           nil,
   316  			wantPurposeFields: []string{"purpose1"},
   317  		},
   318  		{
   319  			description:       "gdpr.purpose1.enforce_purpose is set but invalid",
   320  			giveConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_purpose":}}}`),
   321  			wantConfig:        nil,
   322  			wantErr:           jsonparser.MalformedJsonError,
   323  			wantPurposeFields: nil,
   324  		},
   325  		{
   326  			description:       "gdpr.purpose1.enforce_algo is set",
   327  			giveConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full"}}}`),
   328  			wantConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full"}}}`),
   329  			wantErr:           nil,
   330  			wantPurposeFields: nil,
   331  		},
   332  		{
   333  			description:       "gdpr.purpose1.enforce_purpose is set to string and enforce_algo is set",
   334  			giveConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":"full"}}}`),
   335  			wantConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_algo":"full", "enforce_purpose":"full"}}}`),
   336  			wantErr:           nil,
   337  			wantPurposeFields: []string{"purpose1"},
   338  		},
   339  		{
   340  			description:       "gdpr.purpose1.enforce_purpose is set to string and enforce_algo is set but invalid",
   341  			giveConfig:        []byte(`{"gdpr":{"purpose1":{"enforce_algo":, "enforce_purpose":"full"}}}`),
   342  			wantConfig:        nil,
   343  			wantErr:           jsonparser.MalformedJsonError,
   344  			wantPurposeFields: []string{"purpose1"},
   345  		},
   346  		{
   347  			description: "gdpr.purpose{1-10}.enforce_purpose are set to strings no and full alternating",
   348  			giveConfig: []byte(`{"gdpr":{` +
   349  				`"purpose1":` + enforcePurposeNo +
   350  				`,"purpose2":` + enforcePurposeFull +
   351  				`,"purpose3":` + enforcePurposeNo +
   352  				`,"purpose4":` + enforcePurposeFull +
   353  				`,"purpose5":` + enforcePurposeNo +
   354  				`,"purpose6":` + enforcePurposeFull +
   355  				`,"purpose7":` + enforcePurposeNo +
   356  				`,"purpose8":` + enforcePurposeFull +
   357  				`,"purpose9":` + enforcePurposeNo +
   358  				`,"purpose10":` + enforcePurposeFull +
   359  				`}}`),
   360  			wantConfig: []byte(`{"gdpr":{` +
   361  				`"purpose1":` + enforcePurposeNoMapped +
   362  				`,"purpose2":` + enforcePurposeFullMapped +
   363  				`,"purpose3":` + enforcePurposeNoMapped +
   364  				`,"purpose4":` + enforcePurposeFullMapped +
   365  				`,"purpose5":` + enforcePurposeNoMapped +
   366  				`,"purpose6":` + enforcePurposeFullMapped +
   367  				`,"purpose7":` + enforcePurposeNoMapped +
   368  				`,"purpose8":` + enforcePurposeFullMapped +
   369  				`,"purpose9":` + enforcePurposeNoMapped +
   370  				`,"purpose10":` + enforcePurposeFullMapped +
   371  				`}}`),
   372  			wantErr:           nil,
   373  			wantPurposeFields: []string{"purpose1", "purpose2", "purpose3", "purpose4", "purpose5", "purpose6", "purpose7", "purpose8", "purpose9", "purpose10"},
   374  		},
   375  	}
   376  
   377  	for _, tt := range tests {
   378  		metricsMock := &metrics.MetricsEngineMock{}
   379  		metricsMock.Mock.On("RecordAccountGDPRPurposeWarning", mock.Anything, mock.Anything).Return()
   380  		metricsMock.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return()
   381  
   382  		newConfig, err, deprecatedPurposeFields := ConvertGDPREnforcePurposeFields(tt.giveConfig)
   383  		if tt.wantErr != nil {
   384  			assert.Error(t, err, tt.description)
   385  		}
   386  
   387  		if len(tt.wantConfig) == 0 {
   388  			assert.Equal(t, tt.wantConfig, newConfig, tt.description)
   389  		} else {
   390  			assert.JSONEq(t, string(tt.wantConfig), string(newConfig), tt.description)
   391  		}
   392  		assert.Equal(t, tt.wantPurposeFields, deprecatedPurposeFields, tt.description)
   393  	}
   394  }
   395  
   396  func TestGdprCcpaChannelEnabledMetrics(t *testing.T) {
   397  	cfg := &config.Configuration{}
   398  	fetcher := &mockAccountFetcher{}
   399  	assert.NoError(t, cfg.MarshalAccountDefaults())
   400  
   401  	testCases := []struct {
   402  		name                string
   403  		givenAccountID      string
   404  		givenMetric         string
   405  		expectedMetricCount int
   406  	}{
   407  		{
   408  			name:                "ChannelEnabledGDPR",
   409  			givenAccountID:      "gdpr_channel_enabled_acct",
   410  			givenMetric:         "RecordAccountGDPRChannelEnabledWarning",
   411  			expectedMetricCount: 1,
   412  		},
   413  		{
   414  			name:                "ChannelEnabledCCPA",
   415  			givenAccountID:      "ccpa_channel_enabled_acct",
   416  			givenMetric:         "RecordAccountCCPAChannelEnabledWarning",
   417  			expectedMetricCount: 1,
   418  		},
   419  		{
   420  			name:                "NotChannelEnabledCCPA",
   421  			givenAccountID:      "valid_acct",
   422  			givenMetric:         "RecordAccountCCPAChannelEnabledWarning",
   423  			expectedMetricCount: 0,
   424  		},
   425  		{
   426  			name:                "NotChannelEnabledGDPR",
   427  			givenAccountID:      "valid_acct",
   428  			givenMetric:         "RecordAccountGDPRChannelEnabledWarning",
   429  			expectedMetricCount: 0,
   430  		},
   431  	}
   432  
   433  	for _, test := range testCases {
   434  		t.Run(test.name, func(t *testing.T) {
   435  			metrics := &metrics.MetricsEngineMock{}
   436  			metrics.Mock.On(test.givenMetric, mock.Anything, mock.Anything).Return()
   437  			metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return()
   438  
   439  			_, _ = GetAccount(context.Background(), cfg, fetcher, test.givenAccountID, metrics)
   440  
   441  			metrics.AssertNumberOfCalls(t, test.givenMetric, test.expectedMetricCount)
   442  		})
   443  	}
   444  }
   445  
   446  func TestGdprPurposeWarningMetrics(t *testing.T) {
   447  	cfg := &config.Configuration{}
   448  	fetcher := &mockAccountFetcher{}
   449  	assert.NoError(t, cfg.MarshalAccountDefaults())
   450  
   451  	testCases := []struct {
   452  		name                string
   453  		givenMetric         string
   454  		givenAccountID      string
   455  		givenConfig         []byte
   456  		expectedMetricCount int
   457  	}{
   458  		{
   459  			name:                "Purpose1MetricCalled",
   460  			givenAccountID:      "gdpr_deprecated_purpose1",
   461  			expectedMetricCount: 1,
   462  		},
   463  		{
   464  			name:                "Purpose2MetricCalled",
   465  			givenAccountID:      "gdpr_deprecated_purpose2",
   466  			expectedMetricCount: 1,
   467  		},
   468  		{
   469  			name:                "Purpose3MetricCalled",
   470  			givenAccountID:      "gdpr_deprecated_purpose3",
   471  			expectedMetricCount: 1,
   472  		},
   473  		{
   474  			name:                "Purpose4MetricCalled",
   475  			givenAccountID:      "gdpr_deprecated_purpose4",
   476  			expectedMetricCount: 1,
   477  		},
   478  		{
   479  			name:                "Purpose5MetricCalled",
   480  			givenAccountID:      "gdpr_deprecated_purpose5",
   481  			expectedMetricCount: 1,
   482  		},
   483  		{
   484  			name:                "Purpose6MetricCalled",
   485  			givenAccountID:      "gdpr_deprecated_purpose6",
   486  			expectedMetricCount: 1,
   487  		},
   488  		{
   489  			name:                "Purpose7MetricCalled",
   490  			givenAccountID:      "gdpr_deprecated_purpose7",
   491  			expectedMetricCount: 1,
   492  		},
   493  		{
   494  			name:                "Purpose8MetricCalled",
   495  			givenAccountID:      "gdpr_deprecated_purpose8",
   496  			expectedMetricCount: 1,
   497  		},
   498  		{
   499  			name:                "Purpose9MetricCalled",
   500  			givenAccountID:      "gdpr_deprecated_purpose9",
   501  			expectedMetricCount: 1,
   502  		},
   503  		{
   504  			name:                "Purpose10MetricCalled",
   505  			givenAccountID:      "gdpr_deprecated_purpose10",
   506  			expectedMetricCount: 1,
   507  		},
   508  		{
   509  			name:                "PurposeMetricNotCalled",
   510  			givenAccountID:      "valid_acct",
   511  			expectedMetricCount: 0,
   512  		},
   513  	}
   514  
   515  	for _, test := range testCases {
   516  		t.Run(test.name, func(t *testing.T) {
   517  			metrics := &metrics.MetricsEngineMock{}
   518  			metrics.Mock.On("RecordAccountGDPRPurposeWarning", mock.Anything, mock.Anything).Return()
   519  			metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return()
   520  
   521  			_, _ = GetAccount(context.Background(), cfg, fetcher, test.givenAccountID, metrics)
   522  
   523  			metrics.AssertNumberOfCalls(t, "RecordAccountGDPRPurposeWarning", test.expectedMetricCount)
   524  		})
   525  
   526  	}
   527  }
   528  
   529  func TestAccountUpgradeStatusGetAccount(t *testing.T) {
   530  	cfg := &config.Configuration{}
   531  	fetcher := &mockAccountFetcher{}
   532  	assert.NoError(t, cfg.MarshalAccountDefaults())
   533  
   534  	testCases := []struct {
   535  		name                string
   536  		givenAccountIDs     []string
   537  		givenMetrics        []string
   538  		expectedMetricCount int
   539  	}{
   540  		{
   541  			name:                "MultipleDeprecatedConfigs",
   542  			givenAccountIDs:     []string{"gdpr_channel_enabled_deprecated_purpose_acct"},
   543  			givenMetrics:        []string{"RecordAccountGDPRChannelEnabledWarning", "RecordAccountGDPRPurposeWarning"},
   544  			expectedMetricCount: 1,
   545  		},
   546  		{
   547  			name:                "ZeroDeprecatedConfigs",
   548  			givenAccountIDs:     []string{"valid_acct"},
   549  			givenMetrics:        []string{},
   550  			expectedMetricCount: 0,
   551  		},
   552  		{
   553  			name:                "OneDeprecatedConfigPurpose",
   554  			givenAccountIDs:     []string{"gdpr_deprecated_purpose1"},
   555  			givenMetrics:        []string{"RecordAccountGDPRPurposeWarning"},
   556  			expectedMetricCount: 1,
   557  		},
   558  		{
   559  			name:                "OneDeprecatedConfigGDPRChannelEnabled",
   560  			givenAccountIDs:     []string{"gdpr_channel_enabled_acct"},
   561  			givenMetrics:        []string{"RecordAccountGDPRChannelEnabledWarning"},
   562  			expectedMetricCount: 1,
   563  		},
   564  		{
   565  			name:                "OneDeprecatedConfigCCPAChannelEnabled",
   566  			givenAccountIDs:     []string{"ccpa_channel_enabled_acct"},
   567  			givenMetrics:        []string{"RecordAccountCCPAChannelEnabledWarning"},
   568  			expectedMetricCount: 1,
   569  		},
   570  		{
   571  			name:                "MultipleAccountsWithDeprecatedConfigs",
   572  			givenAccountIDs:     []string{"gdpr_channel_enabled_acct", "gdpr_deprecated_purpose1"},
   573  			givenMetrics:        []string{"RecordAccountGDPRChannelEnabledWarning", "RecordAccountGDPRPurposeWarning"},
   574  			expectedMetricCount: 2,
   575  		},
   576  	}
   577  
   578  	for _, test := range testCases {
   579  		t.Run(test.name, func(t *testing.T) {
   580  			metrics := &metrics.MetricsEngineMock{}
   581  			for _, metric := range test.givenMetrics {
   582  				metrics.Mock.On(metric, mock.Anything, mock.Anything).Return()
   583  			}
   584  			metrics.Mock.On("RecordAccountUpgradeStatus", mock.Anything, mock.Anything).Return()
   585  
   586  			for _, accountID := range test.givenAccountIDs {
   587  				_, _ = GetAccount(context.Background(), cfg, fetcher, accountID, metrics)
   588  			}
   589  			metrics.AssertNumberOfCalls(t, "RecordAccountUpgradeStatus", test.expectedMetricCount)
   590  		})
   591  	}
   592  }
   593  
   594  func TestDeprecateEventsEnabledField(t *testing.T) {
   595  	testCases := []struct {
   596  		name    string
   597  		account *config.Account
   598  		want    *bool
   599  	}{
   600  		{
   601  			name:    "account is nil",
   602  			account: nil,
   603  			want:    nil,
   604  		},
   605  		{
   606  			name: "account.EventsEnabled is nil, account.Events.Enabled is nil",
   607  			account: &config.Account{
   608  				EventsEnabled: nil,
   609  				Events: config.Events{
   610  					Enabled: nil,
   611  				},
   612  			},
   613  			want: nil,
   614  		},
   615  		{
   616  			name: "account.EventsEnabled is nil, account.Events.Enabled is non-nil",
   617  			account: &config.Account{
   618  				EventsEnabled: nil,
   619  				Events: config.Events{
   620  					Enabled: ptrutil.ToPtr(true),
   621  				},
   622  			},
   623  			want: ptrutil.ToPtr(true),
   624  		},
   625  		{
   626  			name: "account.EventsEnabled is non-nil, account.Events.Enabled is nil",
   627  			account: &config.Account{
   628  				EventsEnabled: ptrutil.ToPtr(true),
   629  				Events: config.Events{
   630  					Enabled: nil,
   631  				},
   632  			},
   633  			want: ptrutil.ToPtr(true),
   634  		},
   635  		{
   636  			name: "account.EventsEnabled is non-nil, account.Events.Enabled is non-nil",
   637  			account: &config.Account{
   638  				EventsEnabled: ptrutil.ToPtr(false),
   639  				Events: config.Events{
   640  					Enabled: ptrutil.ToPtr(true),
   641  				},
   642  			},
   643  			want: ptrutil.ToPtr(true),
   644  		},
   645  	}
   646  
   647  	for _, test := range testCases {
   648  		t.Run(test.name, func(t *testing.T) {
   649  			deprecateEventsEnabledField(test.account)
   650  			if test.account != nil {
   651  				assert.Equal(t, test.want, test.account.Events.Enabled)
   652  			}
   653  		})
   654  	}
   655  }