github.com/prebid/prebid-server/v2@v2.18.0/analytics/pubstack/pubstack_module_test.go (about) 1 package pubstack 2 3 import ( 4 "net/http" 5 "net/http/httptest" 6 "os" 7 "testing" 8 "time" 9 10 "github.com/benbjohnson/clock" 11 "github.com/prebid/prebid-server/v2/analytics" 12 "github.com/prebid/prebid-server/v2/openrtb_ext" 13 "github.com/stretchr/testify/assert" 14 ) 15 16 func TestNewModuleErrors(t *testing.T) { 17 tests := []struct { 18 description string 19 refreshDelay string 20 maxByteSize string 21 maxTime string 22 }{ 23 { 24 description: "refresh delay is in an invalid format", 25 refreshDelay: "1invalid", 26 maxByteSize: "90MB", 27 maxTime: "15m", 28 }, 29 { 30 description: "max byte size is in an invalid format", 31 refreshDelay: "1h", 32 maxByteSize: "90invalid", 33 maxTime: "15m", 34 }, 35 { 36 description: "max time is in an invalid format", 37 refreshDelay: "1h", 38 maxByteSize: "90MB", 39 maxTime: "15invalid", 40 }, 41 } 42 43 for _, tt := range tests { 44 _, err := NewModule(&http.Client{}, "scope", "http://example.com", tt.refreshDelay, 100, tt.maxByteSize, tt.maxTime, clock.NewMock()) 45 assert.Error(t, err, tt.description) 46 } 47 } 48 49 func TestNewModuleSuccess(t *testing.T) { 50 tests := []struct { 51 description string 52 feature string 53 logObject func(analytics.Module) 54 }{ 55 { 56 description: "auction events are only published when logging an auction object with auction feature on", 57 feature: auction, 58 logObject: func(module analytics.Module) { 59 module.LogAuctionObject(&analytics.AuctionObject{Status: http.StatusOK}) 60 }, 61 }, 62 { 63 description: "AMP events are only published when logging an AMP object with AMP feature on", 64 feature: amp, 65 logObject: func(module analytics.Module) { 66 module.LogAmpObject(&analytics.AmpObject{Status: http.StatusOK}) 67 }, 68 }, 69 { 70 description: "video events are only published when logging a video object with video feature on", 71 feature: video, 72 logObject: func(module analytics.Module) { 73 module.LogVideoObject(&analytics.VideoObject{Status: http.StatusOK}) 74 }, 75 }, 76 { 77 description: "cookie events are only published when logging a cookie object with cookie feature on", 78 feature: cookieSync, 79 logObject: func(module analytics.Module) { 80 module.LogCookieSyncObject(&analytics.CookieSyncObject{Status: http.StatusOK}) 81 }, 82 }, 83 { 84 description: "setUID events are only published when logging a setUID object with setUID feature on", 85 feature: setUID, 86 logObject: func(module analytics.Module) { 87 module.LogSetUIDObject(&analytics.SetUIDObject{Status: http.StatusOK}) 88 }, 89 }, 90 { 91 description: "Ignore excluded fields from marshal", 92 feature: auction, 93 logObject: func(module analytics.Module) { 94 module.LogAuctionObject(&analytics.AuctionObject{ 95 RequestWrapper: &openrtb_ext.RequestWrapper{}, 96 SeatNonBid: []openrtb_ext.SeatNonBid{ 97 { 98 NonBid: []openrtb_ext.NonBid{ 99 { 100 ImpId: "123", 101 StatusCode: 34, 102 Ext: openrtb_ext.NonBidExt{Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{}}}, 103 }, 104 }, 105 }, 106 }, 107 }) 108 }, 109 }, 110 } 111 112 for _, tt := range tests { 113 // original config with the feature disabled so no events should be sent 114 origConfig := &Configuration{ 115 Features: map[string]bool{ 116 tt.feature: false, 117 }, 118 } 119 120 // updated config with the feature enabled so events should be sent 121 updatedConfig := &Configuration{ 122 Features: map[string]bool{ 123 tt.feature: true, 124 }, 125 } 126 127 // create server with an intake endpoint that PBS hits when events are sent 128 mux := http.NewServeMux() 129 intakeChannel := make(chan int) 130 mux.HandleFunc("/intake/"+tt.feature+"/", func(res http.ResponseWriter, req *http.Request) { 131 intakeChannel <- 1 132 }) 133 server := httptest.NewServer(mux) 134 client := server.Client() 135 136 // set the event server url on each of the configs 137 origConfig.Endpoint = server.URL 138 updatedConfig.Endpoint = server.URL 139 140 // instantiate module with a manual config update task 141 clockMock := clock.NewMock() 142 configTask := fakeConfigUpdateTask{} 143 module, err := NewModuleWithConfigTask(client, "scope", server.URL, 100, "1B", "1s", &configTask, clockMock) 144 assert.NoError(t, err, tt.description) 145 146 pubstack, _ := module.(*PubstackModule) 147 148 // original config 149 configTask.Push(origConfig) 150 time.Sleep(10 * time.Millisecond) // allow time for the module to load the original config 151 tt.logObject(pubstack) // attempt to log; no event channel created because feature is disabled in original config 152 clockMock.Add(1 * time.Second) // trigger event channel sending 153 assertChanNone(t, intakeChannel, tt.description+":original") // verify no event was received 154 155 // updated config 156 configTask.Push(updatedConfig) 157 time.Sleep(10 * time.Millisecond) // allow time for the server to start serving the updated config 158 tt.logObject(pubstack) // attempt to log; event channel should be created because feature is enabled in updated config 159 clockMock.Add(1 * time.Second) // trigger event channel sending 160 assertChanOne(t, intakeChannel, tt.description+":updated") // verify an event was received 161 162 // no config change 163 configTask.Push(updatedConfig) 164 time.Sleep(10 * time.Millisecond) // allow time for the server to determine no config change 165 tt.logObject(pubstack) // attempt to log; event channel should still be created from loading updated config 166 clockMock.Add(1 * time.Second) // trigger event channel sending 167 assertChanOne(t, intakeChannel, tt.description+":no_change") // verify an event was received 168 169 // shutdown 170 pubstack.sigTermCh <- os.Kill // simulate os shutdown signal 171 time.Sleep(10 * time.Millisecond) // allow time for the server to switch to shutdown generated config 172 tt.logObject(pubstack) // attempt to log; event channel should be closed from the os kill signal 173 clockMock.Add(1 * time.Second) // trigger event channel sending 174 assertChanNone(t, intakeChannel, tt.description+":shutdown") // verify no event was received 175 } 176 } 177 178 func assertChanNone(t *testing.T, c <-chan int, msgAndArgs ...interface{}) bool { 179 t.Helper() 180 select { 181 case <-c: 182 return assert.Fail(t, "Should NOT receive an event, but did", msgAndArgs...) 183 case <-time.After(100 * time.Millisecond): 184 return true 185 } 186 } 187 188 func assertChanOne(t *testing.T, c <-chan int, msgAndArgs ...interface{}) bool { 189 t.Helper() 190 select { 191 case <-c: 192 return true 193 case <-time.After(200 * time.Millisecond): 194 return assert.Fail(t, "Should receive an event, but did NOT", msgAndArgs...) 195 } 196 } 197 198 type fakeConfigUpdateTask struct { 199 configChan chan *Configuration 200 } 201 202 func (f *fakeConfigUpdateTask) Start(stop <-chan struct{}) <-chan *Configuration { 203 f.configChan = make(chan *Configuration) 204 return f.configChan 205 } 206 207 func (f *fakeConfigUpdateTask) Push(c *Configuration) { 208 f.configChan <- c 209 }