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 }