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