github.com/prebid/prebid-server/v2@v2.18.0/hooks/hookexecution/enricher_test.go (about)

     1  package hookexecution
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/prebid/openrtb/v20/openrtb2"
    10  	"github.com/prebid/prebid-server/v2/config"
    11  	"github.com/prebid/prebid-server/v2/hooks/hookanalytics"
    12  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    13  	"github.com/prebid/prebid-server/v2/util/jsonutil"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  // StageOutcomeTest is used for test purpose instead of the original structure,
    19  // so we can unmarshal hidden fields such as StageOutcomeTest.Stage and HookOutcomeTest.Errors/Warnings
    20  type StageOutcomeTest struct {
    21  	ExecutionTime
    22  	Entity entity             `json:"entity"`
    23  	Groups []GroupOutcomeTest `json:"groups"`
    24  	Stage  string             `json:"stage"`
    25  }
    26  
    27  type GroupOutcomeTest struct {
    28  	ExecutionTime
    29  	InvocationResults []HookOutcomeTest `json:"invocation_results"`
    30  }
    31  
    32  type HookOutcomeTest struct {
    33  	ExecutionTime
    34  	AnalyticsTags hookanalytics.Analytics `json:"analytics_tags"`
    35  	HookID        HookID                  `json:"hook_id"`
    36  	Status        Status                  `json:"status"`
    37  	Action        Action                  `json:"action"`
    38  	Message       string                  `json:"message"`
    39  	DebugMessages []string                `json:"debug_messages"`
    40  	Errors        []string                `json:"errors"`
    41  	Warnings      []string                `json:"warnings"`
    42  }
    43  
    44  func TestEnrichBidResponse(t *testing.T) {
    45  	testCases := []struct {
    46  		description             string
    47  		expectedWarnings        []error
    48  		expectedBidResponseFile string
    49  		stageOutcomesFile       string
    50  		bidResponse             *openrtb2.BidResponse
    51  		bidRequest              *openrtb2.BidRequest
    52  		account                 *config.Account
    53  	}{
    54  		{
    55  			description:             "BidResponse enriched with verbose trace and debug info when bidRequest.test=1 and trace=verbose",
    56  			expectedWarnings:        nil,
    57  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-verbose-debug-response.json",
    58  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
    59  			bidResponse:             &openrtb2.BidResponse{Ext: []byte(`{"prebid": {"foo": "bar"}}`)},
    60  			bidRequest:              &openrtb2.BidRequest{Test: 1, Ext: []byte(`{"prebid": {"trace": "verbose"}}`)},
    61  			account:                 &config.Account{DebugAllow: true},
    62  		},
    63  		{
    64  			description:             "BidResponse enriched with basic trace and debug info when bidRequest.ext.prebid.debug=true and trace=basic",
    65  			expectedWarnings:        nil,
    66  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-basic-debug-response.json",
    67  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
    68  			bidResponse:             &openrtb2.BidResponse{Ext: []byte(`{"prebid": {"foo": "bar"}}`)},
    69  			bidRequest:              &openrtb2.BidRequest{Ext: []byte(`{"prebid": {"debug": true, "trace": "basic"}}`)},
    70  			account:                 &config.Account{DebugAllow: true},
    71  		},
    72  		{
    73  			description:             "BidResponse enriched with debug info when bidRequest.ext.prebid.debug=true",
    74  			expectedWarnings:        nil,
    75  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-debug-response.json",
    76  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
    77  			bidResponse:             &openrtb2.BidResponse{Ext: []byte(`{"prebid": {"foo": "bar"}}`)},
    78  			bidRequest:              &openrtb2.BidRequest{Ext: []byte(`{"prebid": {"debug": true, "trace": ""}}`)},
    79  			account:                 &config.Account{DebugAllow: true},
    80  		},
    81  		{
    82  			description:             "BidResponse not enriched when bidRequest.ext.prebid.debug=false",
    83  			expectedWarnings:        nil,
    84  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-empty-response.json",
    85  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
    86  			bidResponse:             &openrtb2.BidResponse{Ext: []byte(`{"prebid": {"foo": "bar"}}`)},
    87  			bidRequest:              &openrtb2.BidRequest{},
    88  			account:                 &config.Account{DebugAllow: true},
    89  		},
    90  		{
    91  			description:             "BidResponse enriched only with verbose trace when bidRequest.ext.prebid.trace=verbose and account.DebugAllow=false",
    92  			expectedWarnings:        nil,
    93  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-verbose-response.json",
    94  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
    95  			bidResponse:             &openrtb2.BidResponse{Ext: []byte(`{"prebid": {"foo": "bar"}}`)},
    96  			bidRequest:              &openrtb2.BidRequest{Test: 1, Ext: []byte(`{"prebid": {"debug": true, "trace": "verbose"}}`)},
    97  			account:                 &config.Account{DebugAllow: false},
    98  		},
    99  		{
   100  			description:             "BidResponse enriched with debug info if bidResponse.Ext is nil",
   101  			expectedWarnings:        nil,
   102  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-pure-debug-response.json",
   103  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   104  			bidResponse:             &openrtb2.BidResponse{},
   105  			bidRequest:              &openrtb2.BidRequest{Test: 1},
   106  			account:                 &config.Account{DebugAllow: true},
   107  		},
   108  		{
   109  			description:             "BidResponse not enriched with modules if stage outcome groups empty",
   110  			expectedWarnings:        nil,
   111  			expectedBidResponseFile: "test/empty-stage-outcomes/empty.json",
   112  			stageOutcomesFile:       "test/empty-stage-outcomes/empty-stage-outcomes-v1.json",
   113  			bidResponse:             &openrtb2.BidResponse{},
   114  			bidRequest:              &openrtb2.BidRequest{Test: 1},
   115  			account:                 &config.Account{DebugAllow: true},
   116  		},
   117  		{
   118  			description:             "BidResponse not enriched with modules if stage outcomes empty",
   119  			expectedWarnings:        nil,
   120  			expectedBidResponseFile: "test/empty-stage-outcomes/empty.json",
   121  			stageOutcomesFile:       "test/empty-stage-outcomes/empty-stage-outcomes-v2.json",
   122  			bidResponse:             &openrtb2.BidResponse{},
   123  			bidRequest:              &openrtb2.BidRequest{Test: 1},
   124  			account:                 &config.Account{DebugAllow: true},
   125  		},
   126  	}
   127  
   128  	for _, test := range testCases {
   129  		t.Run(test.description, func(t *testing.T) {
   130  			expectedResponse := readFile(t, test.expectedBidResponseFile)
   131  			stageOutcomes := getStageOutcomes(t, test.stageOutcomesFile)
   132  
   133  			ext, warns, err := EnrichExtBidResponse(test.bidResponse.Ext, stageOutcomes, test.bidRequest, test.account)
   134  			require.NoError(t, err, "Failed to enrich BidResponse with hook debug information: %s", err)
   135  			assert.Equal(t, test.expectedWarnings, warns, "Unexpected warnings")
   136  
   137  			test.bidResponse.Ext = ext
   138  			if test.bidResponse.Ext == nil {
   139  				assert.Empty(t, expectedResponse)
   140  			} else {
   141  				assert.JSONEq(t, string(expectedResponse), string(test.bidResponse.Ext))
   142  			}
   143  		})
   144  	}
   145  }
   146  
   147  func TestGetModulesJSON(t *testing.T) {
   148  	testCases := []struct {
   149  		description             string
   150  		expectedWarnings        []error
   151  		expectedBidResponseFile string
   152  		stageOutcomesFile       string
   153  		bidRequest              *openrtb2.BidRequest
   154  		account                 *config.Account
   155  	}{
   156  		{
   157  			description:             "Modules Outcome contains verbose trace and debug info when bidRequest.test=1 and trace=verbose",
   158  			expectedWarnings:        nil,
   159  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-verbose-debug-response.json",
   160  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   161  			bidRequest:              &openrtb2.BidRequest{Test: 1, Ext: []byte(`{"prebid": {"trace": "verbose"}}`)},
   162  			account:                 &config.Account{DebugAllow: true},
   163  		},
   164  		{
   165  			description:             "Modules Outcome contains verbose trace and debug info when bidRequest.test=1 and trace=verbose and account is not defined",
   166  			expectedWarnings:        nil,
   167  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-verbose-debug-response.json",
   168  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   169  			bidRequest:              &openrtb2.BidRequest{Test: 1, Ext: []byte(`{"prebid": {"trace": "verbose"}}`)},
   170  			account:                 nil,
   171  		},
   172  		{
   173  			description:             "Modules Outcome contains basic trace and debug info when bidRequest.ext.prebid.debug=true and trace=basic",
   174  			expectedWarnings:        nil,
   175  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-basic-debug-response.json",
   176  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   177  			bidRequest:              &openrtb2.BidRequest{Ext: []byte(`{"prebid": {"debug": true, "trace": "basic"}}`)},
   178  			account:                 &config.Account{DebugAllow: true},
   179  		},
   180  		{
   181  			description:             "Modules Outcome contains debug info when bidRequest.ext.prebid.debug=true",
   182  			expectedWarnings:        nil,
   183  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-debug-response.json",
   184  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   185  			bidRequest:              &openrtb2.BidRequest{Ext: []byte(`{"prebid": {"debug": true, "trace": ""}}`)},
   186  			account:                 &config.Account{DebugAllow: true},
   187  		},
   188  		{
   189  			description:             "Modules Outcome empty when bidRequest.ext.prebid.debug=false",
   190  			expectedWarnings:        nil,
   191  			expectedBidResponseFile: "test/empty-stage-outcomes/empty.json",
   192  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   193  			bidRequest:              &openrtb2.BidRequest{},
   194  			account:                 &config.Account{DebugAllow: true},
   195  		},
   196  		{
   197  			description:             "Modules Outcome empty when bidRequest is nil",
   198  			expectedWarnings:        nil,
   199  			expectedBidResponseFile: "test/empty-stage-outcomes/empty.json",
   200  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   201  			bidRequest:              nil,
   202  			account:                 &config.Account{DebugAllow: true},
   203  		},
   204  		{
   205  			description:             "Modules Outcome contains only verbose trace when bidRequest.ext.prebid.trace=verbose and account.DebugAllow=false",
   206  			expectedWarnings:        nil,
   207  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-verbose-response.json",
   208  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   209  			bidRequest:              &openrtb2.BidRequest{Test: 1, Ext: []byte(`{"prebid": {"debug": true, "trace": "verbose"}}`)},
   210  			account:                 &config.Account{DebugAllow: false},
   211  		},
   212  		{
   213  			description:             "Modules Outcome contains debug info if bidResponse.Ext is nil",
   214  			expectedWarnings:        nil,
   215  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-pure-debug-response.json",
   216  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   217  			bidRequest:              &openrtb2.BidRequest{Test: 1},
   218  			account:                 &config.Account{DebugAllow: true},
   219  		},
   220  		{
   221  			description:             "Modules Outcome empty if stage outcome groups empty",
   222  			expectedWarnings:        nil,
   223  			expectedBidResponseFile: "test/empty-stage-outcomes/empty.json",
   224  			stageOutcomesFile:       "test/empty-stage-outcomes/empty-stage-outcomes-v1.json",
   225  			bidRequest:              &openrtb2.BidRequest{Test: 1},
   226  			account:                 &config.Account{DebugAllow: true},
   227  		},
   228  		{
   229  			description:             "Modules Outcome empty if stage outcomes empty",
   230  			expectedWarnings:        nil,
   231  			expectedBidResponseFile: "test/empty-stage-outcomes/empty.json",
   232  			stageOutcomesFile:       "test/empty-stage-outcomes/empty-stage-outcomes-v2.json",
   233  			bidRequest:              &openrtb2.BidRequest{Test: 1},
   234  			account:                 &config.Account{DebugAllow: true},
   235  		},
   236  		{
   237  			description: "Warnings returned if debug info invalid",
   238  			expectedWarnings: []error{
   239  				errors.New("Value is not a string: 1"),
   240  				errors.New("Value is not a boolean: active"),
   241  			},
   242  			expectedBidResponseFile: "test/complete-stage-outcomes/expected-pure-debug-response.json",
   243  			stageOutcomesFile:       "test/complete-stage-outcomes/stage-outcomes.json",
   244  			bidRequest:              &openrtb2.BidRequest{Test: 1, Ext: json.RawMessage(`{"prebid": {"debug": "active", "trace": 1}}`)},
   245  			account:                 &config.Account{DebugAllow: true},
   246  		},
   247  	}
   248  
   249  	for _, test := range testCases {
   250  		t.Run(test.description, func(t *testing.T) {
   251  			expectedResponse := readFile(t, test.expectedBidResponseFile)
   252  			stageOutcomes := getStageOutcomes(t, test.stageOutcomesFile)
   253  
   254  			modules, warns, err := GetModulesJSON(stageOutcomes, test.bidRequest, test.account)
   255  			require.NoError(t, err, "Failed to get modules outcome as json: %s", err)
   256  			assert.Equal(t, test.expectedWarnings, warns, "Unexpected warnings")
   257  
   258  			if modules == nil {
   259  				assert.Empty(t, expectedResponse)
   260  			} else {
   261  				var expectedExtBidResponse openrtb_ext.ExtBidResponse
   262  				err := jsonutil.UnmarshalValid(expectedResponse, &expectedExtBidResponse)
   263  				assert.NoError(t, err, "Failed to unmarshal prebid response extension")
   264  				assert.JSONEq(t, string(expectedExtBidResponse.Prebid.Modules), string(modules))
   265  			}
   266  		})
   267  	}
   268  }
   269  
   270  func getStageOutcomes(t *testing.T, file string) []StageOutcome {
   271  	var stageOutcomes []StageOutcome
   272  	var stageOutcomesTest []StageOutcomeTest
   273  
   274  	data := readFile(t, file)
   275  	err := jsonutil.UnmarshalValid(data, &stageOutcomesTest)
   276  	require.NoError(t, err, "Failed to unmarshal stage outcomes: %s", err)
   277  
   278  	for _, stageT := range stageOutcomesTest {
   279  		stage := StageOutcome{
   280  			ExecutionTime: stageT.ExecutionTime,
   281  			Entity:        stageT.Entity,
   282  			Stage:         stageT.Stage,
   283  		}
   284  
   285  		for _, groupT := range stageT.Groups {
   286  			group := GroupOutcome{ExecutionTime: groupT.ExecutionTime}
   287  			for _, hookT := range groupT.InvocationResults {
   288  				group.InvocationResults = append(group.InvocationResults, HookOutcome(hookT))
   289  			}
   290  
   291  			stage.Groups = append(stage.Groups, group)
   292  		}
   293  		stageOutcomes = append(stageOutcomes, stage)
   294  	}
   295  
   296  	return stageOutcomes
   297  }
   298  
   299  func readFile(t *testing.T, filename string) []byte {
   300  	data, err := os.ReadFile(filename)
   301  	require.NoError(t, err, "Failed to read file %s: %v", filename, err)
   302  	return data
   303  }