@@ -2,11 +2,9 @@ package pubstack
2
2
3
3
import (
4
4
"encoding/json"
5
- "github.com/prebid/prebid-server/analytics/pubstack/eventchannel"
6
5
"io/ioutil"
7
6
"net/http"
8
7
"net/http/httptest"
9
- "net/url"
10
8
"os"
11
9
"testing"
12
10
"time"
@@ -16,7 +14,7 @@ import (
16
14
"github.com/stretchr/testify/assert"
17
15
)
18
16
19
- func loadJsonFromFile () (* analytics.AuctionObject , error ) {
17
+ func loadJSONFromFile () (* analytics.AuctionObject , error ) {
20
18
req , err := os .Open ("mocks/mock_openrtb_request.json" )
21
19
if err != nil {
22
20
return nil , err
@@ -57,130 +55,171 @@ func loadJsonFromFile() (*analytics.AuctionObject, error) {
57
55
}, nil
58
56
}
59
57
60
- func TestPubstackModule (t * testing.T ) {
61
-
62
- remoteConfig := & Configuration {}
63
- server := httptest .NewServer (http .HandlerFunc (func (res http.ResponseWriter , req * http.Request ) {
64
- defer req .Body .Close ()
65
- data , _ := json .Marshal (remoteConfig )
66
- res .Write (data )
67
- }))
68
- client := server .Client ()
69
-
70
- defer server .Close ()
71
-
72
- // Loading Issues
73
- _ , err := NewPubstackModule (client , "scope" , server .URL , "1z" , 100 , "90MB" , "15m" )
74
- assert .NotNil (t , err ) // should raise an error since we can't parse args // configRefreshDelay
75
-
76
- _ , err = NewPubstackModule (client , "scope" , server .URL , "1h" , 100 , "90z" , "15m" )
77
- assert .NotNil (t , err ) // should raise an error since we can't parse args // maxByte
78
-
79
- _ , err = NewPubstackModule (client , "scope" , server .URL , "1h" , 100 , "90MB" , "15z" )
80
- assert .NotNil (t , err ) // should raise an error since we can't parse args // maxTime
81
-
82
- // Loading OK
83
- module , err := NewPubstackModule (client , "scope" , server .URL , "10ms" , 100 , "90MB" , "15m" )
84
- assert .Nil (t , err )
85
-
86
- // Default Configuration
87
- pubstack , ok := module .(* PubstackModule )
88
- assert .Equal (t , ok , true ) //PBSAnalyticsModule is also a PubstackModule
89
- assert .Equal (t , len (pubstack .cfg .Features ), 5 )
90
- assert .Equal (t , pubstack .cfg .Features [auction ], false )
91
- assert .Equal (t , pubstack .cfg .Features [video ], false )
92
- assert .Equal (t , pubstack .cfg .Features [amp ], false )
93
- assert .Equal (t , pubstack .cfg .Features [setUID ], false )
94
- assert .Equal (t , pubstack .cfg .Features [cookieSync ], false )
95
-
96
- assert .Equal (t , len (pubstack .eventChannels ), 0 )
97
-
98
- // Process Auction Event
99
- counter := 0
100
- send := func (_ []byte ) error {
101
- counter ++
102
- return nil
58
+ func TestPubstackModuleErrors (t * testing.T ) {
59
+ tests := []struct {
60
+ description string
61
+ refreshDelay string
62
+ maxByteSize string
63
+ maxTime string
64
+ }{
65
+ {
66
+ description : "refresh delay is in an invalid format" ,
67
+ refreshDelay : "1invalid" ,
68
+ maxByteSize : "90MB" ,
69
+ maxTime : "15m" ,
70
+ },
71
+ {
72
+ description : "max byte size is in an invalid format" ,
73
+ refreshDelay : "1h" ,
74
+ maxByteSize : "90invalid" ,
75
+ maxTime : "15m" ,
76
+ },
77
+ {
78
+ description : "max time is in an invalid format" ,
79
+ refreshDelay : "1h" ,
80
+ maxByteSize : "90MB" ,
81
+ maxTime : "15invalid" ,
82
+ },
103
83
}
104
- mockedEvent , err := loadJsonFromFile ()
105
- if err != nil {
106
- t .Fail ()
84
+
85
+ for _ , tt := range tests {
86
+ _ , err := NewPubstackModule (& http.Client {}, "scope" , "http://example.com" , tt .refreshDelay , 100 , tt .maxByteSize , tt .maxTime )
87
+ assert .NotNil (t , err , tt .description )
107
88
}
89
+ }
108
90
109
- pubstack .eventChannels [auction ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
110
- pubstack .eventChannels [video ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
111
- pubstack .eventChannels [amp ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
112
- pubstack .eventChannels [setUID ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
113
- pubstack .eventChannels [cookieSync ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
114
-
115
- pubstack .LogAuctionObject (mockedEvent )
116
- pubstack .LogAmpObject (& analytics.AmpObject {
117
- Status : http .StatusOK ,
118
- })
119
- pubstack .LogCookieSyncObject (& analytics.CookieSyncObject {
120
- Status : http .StatusOK ,
121
- })
122
- pubstack .LogVideoObject (& analytics.VideoObject {
123
- Status : http .StatusOK ,
124
- })
125
- pubstack .LogSetUIDObject (& analytics.SetUIDObject {
126
- Status : http .StatusOK ,
127
- })
128
-
129
- pubstack .closeAllEventChannels ()
130
- time .Sleep (10 * time .Millisecond ) // process channel
131
- assert .Equal (t , counter , 0 )
132
-
133
- // Hot-Reload config
134
- newFeatures := make (map [string ]bool )
135
- newFeatures [auction ] = true
136
- newFeatures [video ] = true
137
- newFeatures [amp ] = true
138
- newFeatures [cookieSync ] = true
139
- newFeatures [setUID ] = true
140
-
141
- remoteConfig = & Configuration {
142
- ScopeID : "new-scope" ,
143
- Endpoint : "new-endpoint" ,
144
- Features : newFeatures ,
91
+ func TestPubstackModuleSuccess (t * testing.T ) {
92
+ tests := []struct {
93
+ description string
94
+ feature string
95
+ logObject func (analytics.PBSAnalyticsModule )
96
+ }{
97
+ {
98
+ description : "auction events are only published when logging an auction object with auction feature on" ,
99
+ feature : auction ,
100
+ logObject : func (module analytics.PBSAnalyticsModule ) {
101
+ module .LogAuctionObject (& analytics.AuctionObject {Status : http .StatusOK })
102
+ },
103
+ },
104
+ {
105
+ description : "AMP events are only published when logging an AMP object with AMP feature on" ,
106
+ feature : amp ,
107
+ logObject : func (module analytics.PBSAnalyticsModule ) {
108
+ module .LogAmpObject (& analytics.AmpObject {Status : http .StatusOK })
109
+ },
110
+ },
111
+ {
112
+ description : "video events are only published when logging a video object with video feature on" ,
113
+ feature : video ,
114
+ logObject : func (module analytics.PBSAnalyticsModule ) {
115
+ module .LogVideoObject (& analytics.VideoObject {Status : http .StatusOK })
116
+ },
117
+ },
118
+ {
119
+ description : "cookie events are only published when logging a cookie object with cookie feature on" ,
120
+ feature : cookieSync ,
121
+ logObject : func (module analytics.PBSAnalyticsModule ) {
122
+ module .LogCookieSyncObject (& analytics.CookieSyncObject {Status : http .StatusOK })
123
+ },
124
+ },
125
+ {
126
+ description : "setUID events are only published when logging a setUID object with setUID feature on" ,
127
+ feature : setUID ,
128
+ logObject : func (module analytics.PBSAnalyticsModule ) {
129
+ module .LogSetUIDObject (& analytics.SetUIDObject {Status : http .StatusOK })
130
+ },
131
+ },
145
132
}
146
133
147
- endpoint , _ := url .Parse (server .URL )
148
- pubstack .reloadConfig (endpoint )
149
-
150
- time .Sleep (2 * time .Millisecond ) // process channel
151
- assert .Equal (t , len (pubstack .cfg .Features ), 5 )
152
- assert .Equal (t , pubstack .cfg .Features [auction ], true )
153
- assert .Equal (t , pubstack .cfg .Features [video ], true )
154
- assert .Equal (t , pubstack .cfg .Features [amp ], true )
155
- assert .Equal (t , pubstack .cfg .Features [setUID ], true )
156
- assert .Equal (t , pubstack .cfg .Features [cookieSync ], true )
157
- assert .Equal (t , pubstack .cfg .ScopeID , "new-scope" )
158
- assert .Equal (t , pubstack .cfg .Endpoint , "new-endpoint" )
159
- assert .Equal (t , len (pubstack .eventChannels ), 5 )
160
-
161
- counter = 0
162
- pubstack .eventChannels [auction ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
163
- pubstack .eventChannels [video ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
164
- pubstack .eventChannels [amp ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
165
- pubstack .eventChannels [setUID ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
166
- pubstack .eventChannels [cookieSync ] = eventchannel .NewEventChannel (send , 2000 , 1 , 10 * time .Second )
167
-
168
- pubstack .LogAuctionObject (mockedEvent )
169
- pubstack .LogAmpObject (& analytics.AmpObject {
170
- Status : http .StatusOK ,
171
- })
172
- pubstack .LogCookieSyncObject (& analytics.CookieSyncObject {
173
- Status : http .StatusOK ,
174
- })
175
- pubstack .LogVideoObject (& analytics.VideoObject {
176
- Status : http .StatusOK ,
177
- })
178
- pubstack .LogSetUIDObject (& analytics.SetUIDObject {
179
- Status : http .StatusOK ,
180
- })
181
- pubstack .closeAllEventChannels ()
182
- time .Sleep (10 * time .Millisecond )
183
-
184
- assert .Equal (t , counter , 5 )
134
+ for _ , tt := range tests {
135
+ // original config is loaded when the module is created
136
+ // the feature is disabled so no events should be sent
137
+ origConfig := & Configuration {
138
+ Features : map [string ]bool {
139
+ tt .feature : false ,
140
+ },
141
+ }
142
+ // updated config is hot-reloaded after some time passes
143
+ // the feature is enabled so events should be sent
144
+ updatedConfig := & Configuration {
145
+ Features : map [string ]bool {
146
+ tt .feature : true ,
147
+ },
148
+ }
149
+
150
+ // create server with root endpoint that returns the current config
151
+ // add an intake endpoint that PBS hits when events are sent
152
+ rootCount := 0
153
+ mux := http .NewServeMux ()
154
+ mux .HandleFunc ("/" , func (res http.ResponseWriter , req * http.Request ) {
155
+ rootCount ++
156
+ defer req .Body .Close ()
157
+
158
+ if rootCount > 1 {
159
+ if data , err := json .Marshal (updatedConfig ); err != nil {
160
+ res .WriteHeader (http .StatusBadRequest )
161
+ } else {
162
+ res .Write (data )
163
+ }
164
+ } else {
165
+ if data , err := json .Marshal (origConfig ); err != nil {
166
+ res .WriteHeader (http .StatusBadRequest )
167
+ } else {
168
+ res .Write (data )
169
+ }
170
+ }
171
+ })
172
+
173
+ intakeChannel := make (chan int ) // using a channel rather than examining the count directly to avoid race
174
+ mux .HandleFunc ("/intake/" + tt .feature + "/" , func (res http.ResponseWriter , req * http.Request ) {
175
+ intakeChannel <- 1
176
+ })
177
+ server := httptest .NewServer (mux )
178
+ client := server .Client ()
179
+
180
+ // set the server url on each of the configs
181
+ origConfig .Endpoint = server .URL
182
+ updatedConfig .Endpoint = server .URL
183
+
184
+ // instantiate module with 25ms config refresh rate
185
+ module , err := NewPubstackModule (client , "scope" , server .URL , "15ms" , 100 , "1B" , "10ms" )
186
+ assert .Nil (t , err , tt .description )
187
+
188
+ // allow time for the module to load the original config
189
+ time .Sleep (10 * time .Millisecond )
190
+
191
+ pubstack , _ := module .(* PubstackModule )
192
+ // attempt to log but no event channel was created because the feature is disabled in the original config
193
+ tt .logObject (pubstack )
194
+
195
+ // verify no event was received over a 10ms period
196
+ assertChanNone (t , intakeChannel , tt .description )
197
+
198
+ // allow time for the server to start serving the updated config
199
+ time .Sleep (10 * time .Millisecond )
200
+
201
+ // attempt to log; the event channel should have been created because the feature is enabled in updated config
202
+ tt .logObject (pubstack )
203
+
204
+ // verify an event was received within 10ms
205
+ assertChanOne (t , intakeChannel , tt .description )
206
+ }
207
+ }
185
208
209
+ func assertChanNone (t * testing.T , c <- chan int , msgAndArgs ... interface {}) bool {
210
+ select {
211
+ case <- c :
212
+ return assert .Fail (t , "Should NOT receive an event, but did" , msgAndArgs ... )
213
+ case <- time .After (10 * time .Millisecond ):
214
+ return true
215
+ }
216
+ }
217
+
218
+ func assertChanOne (t * testing.T , c <- chan int , msgAndArgs ... interface {}) bool {
219
+ select {
220
+ case <- c :
221
+ return true
222
+ case <- time .After (10 * time .Millisecond ):
223
+ return assert .Fail (t , "Should receive an event, but did NOT" , msgAndArgs ... )
224
+ }
186
225
}
0 commit comments