66use std:: collections:: HashMap ;
77use std:: time:: Duration ;
88
9- use crate :: client:: ad_response:: { AdImage , AdResponse , AdSpoc , AdTile } ;
9+ use crate :: client:: ad_response:: {
10+ pop_request_hash_from_url, AdImage , AdResponse , AdResponseValue , AdSpoc , AdTile ,
11+ } ;
1012use crate :: client:: config:: AdsClientConfig ;
1113use crate :: error:: { RecordClickError , RecordImpressionError , ReportAdError , RequestAdsError } ;
1214use crate :: http_cache:: { HttpCache , RequestCachePolicy } ;
1315use crate :: mars:: MARSClient ;
1416use ad_request:: { AdPlacementRequest , AdRequest } ;
1517use context_id:: { ContextIDComponent , DefaultContextIdCallback } ;
16- use serde:: de:: DeserializeOwned ;
1718use url:: Url ;
1819use uuid:: Uuid ;
1920
@@ -75,18 +76,33 @@ impl AdsClient {
7576 }
7677 }
7778
79+ #[ cfg( test) ]
80+ pub fn new_with_mars_client ( client : MARSClient ) -> Self {
81+ let context_id_component = ContextIDComponent :: new (
82+ & uuid:: Uuid :: new_v4 ( ) . to_string ( ) ,
83+ 0 ,
84+ false ,
85+ Box :: new ( DefaultContextIdCallback ) ,
86+ ) ;
87+ Self {
88+ context_id_component,
89+ client,
90+ }
91+ }
92+
7893 fn request_ads < T > (
7994 & self ,
8095 ad_placement_requests : Vec < AdPlacementRequest > ,
8196 options : Option < RequestCachePolicy > ,
8297 ) -> Result < AdResponse < T > , RequestAdsError >
8398 where
84- T : DeserializeOwned ,
99+ T : AdResponseValue ,
85100 {
86101 let context_id = self . get_context_id ( ) ?;
87102 let ad_request = AdRequest :: build ( context_id, ad_placement_requests) ?;
88103 let cache_policy = options. unwrap_or_default ( ) ;
89- let response = self . client . fetch_ads ( & ad_request, & cache_policy) ?;
104+ let ( mut response, request_hash) = self . client . fetch_ads ( & ad_request, & cache_policy) ?;
105+ response. add_request_hash_to_callbacks ( & request_hash) ;
90106 Ok ( response)
91107 }
92108
@@ -118,10 +134,18 @@ impl AdsClient {
118134 }
119135
120136 pub fn record_impression ( & self , impression_url : Url ) -> Result < ( ) , RecordImpressionError > {
137+ let mut impression_url = impression_url. clone ( ) ;
138+ if let Some ( request_hash) = pop_request_hash_from_url ( & mut impression_url) {
139+ let _ = self . client . invalidate_cache_by_hash ( & request_hash) ;
140+ }
121141 self . client . record_impression ( impression_url)
122142 }
123143
124144 pub fn record_click ( & self , click_url : Url ) -> Result < ( ) , RecordClickError > {
145+ let mut click_url = click_url. clone ( ) ;
146+ if let Some ( request_hash) = pop_request_hash_from_url ( & mut click_url) {
147+ let _ = self . client . invalidate_cache_by_hash ( & request_hash) ;
148+ }
125149 self . client . record_click ( click_url)
126150 }
127151
@@ -147,9 +171,12 @@ impl AdsClient {
147171
148172#[ cfg( test) ]
149173mod tests {
150- use crate :: test_utils:: {
151- get_example_happy_image_response, get_example_happy_spoc_response,
152- get_example_happy_uatile_response, make_happy_placement_requests,
174+ use crate :: {
175+ client:: config:: Environment ,
176+ test_utils:: {
177+ get_example_happy_image_response, get_example_happy_spoc_response,
178+ get_example_happy_uatile_response, make_happy_placement_requests,
179+ } ,
153180 } ;
154181
155182 use super :: * ;
@@ -173,8 +200,6 @@ mod tests {
173200
174201 #[ test]
175202 fn test_request_image_ads_happy ( ) {
176- use crate :: test_utils:: create_test_client;
177- use context_id:: { ContextIDComponent , DefaultContextIdCallback } ;
178203 viaduct_dev:: init_backend_dev ( ) ;
179204
180205 let expected_response = get_example_happy_image_response ( ) ;
@@ -184,29 +209,18 @@ mod tests {
184209 . with_body ( serde_json:: to_string ( & expected_response) . unwrap ( ) )
185210 . create ( ) ;
186211
187- let mars_client = create_test_client ( mockito:: server_url ( ) ) ;
188- let context_id_component = ContextIDComponent :: new (
189- & uuid:: Uuid :: new_v4 ( ) . to_string ( ) ,
190- 0 ,
191- false ,
192- Box :: new ( DefaultContextIdCallback ) ,
193- ) ;
194- let component = AdsClient {
195- context_id_component,
196- client : mars_client,
197- } ;
212+ let mars_client = MARSClient :: new ( Environment :: Test , None ) ;
213+ let ads_client = AdsClient :: new_with_mars_client ( mars_client) ;
198214
199215 let ad_placement_requests = make_happy_placement_requests ( ) ;
200216
201- let result = component . request_image_ads ( ad_placement_requests, None ) ;
217+ let result = ads_client . request_image_ads ( ad_placement_requests, None ) ;
202218
203219 assert ! ( result. is_ok( ) ) ;
204220 }
205221
206222 #[ test]
207223 fn test_request_spocs_happy ( ) {
208- use crate :: test_utils:: create_test_client;
209- use context_id:: { ContextIDComponent , DefaultContextIdCallback } ;
210224 viaduct_dev:: init_backend_dev ( ) ;
211225
212226 let expected_response = get_example_happy_spoc_response ( ) ;
@@ -216,29 +230,18 @@ mod tests {
216230 . with_body ( serde_json:: to_string ( & expected_response) . unwrap ( ) )
217231 . create ( ) ;
218232
219- let mars_client = create_test_client ( mockito:: server_url ( ) ) ;
220- let context_id_component = ContextIDComponent :: new (
221- & uuid:: Uuid :: new_v4 ( ) . to_string ( ) ,
222- 0 ,
223- false ,
224- Box :: new ( DefaultContextIdCallback ) ,
225- ) ;
226- let component = AdsClient {
227- context_id_component,
228- client : mars_client,
229- } ;
233+ let mars_client = MARSClient :: new ( Environment :: Test , None ) ;
234+ let ads_client = AdsClient :: new_with_mars_client ( mars_client) ;
230235
231236 let ad_placement_requests = make_happy_placement_requests ( ) ;
232237
233- let result = component . request_spoc_ads ( ad_placement_requests, None ) ;
238+ let result = ads_client . request_spoc_ads ( ad_placement_requests, None ) ;
234239
235240 assert ! ( result. is_ok( ) ) ;
236241 }
237242
238243 #[ test]
239244 fn test_request_tiles_happy ( ) {
240- use crate :: test_utils:: create_test_client;
241- use context_id:: { ContextIDComponent , DefaultContextIdCallback } ;
242245 viaduct_dev:: init_backend_dev ( ) ;
243246
244247 let expected_response = get_example_happy_uatile_response ( ) ;
@@ -248,22 +251,55 @@ mod tests {
248251 . with_body ( serde_json:: to_string ( & expected_response) . unwrap ( ) )
249252 . create ( ) ;
250253
251- let mars_client = create_test_client ( mockito:: server_url ( ) ) ;
252- let context_id_component = ContextIDComponent :: new (
253- & uuid:: Uuid :: new_v4 ( ) . to_string ( ) ,
254- 0 ,
255- false ,
256- Box :: new ( DefaultContextIdCallback ) ,
257- ) ;
258- let component = AdsClient {
259- context_id_component,
260- client : mars_client,
261- } ;
254+ let mars_client = MARSClient :: new ( Environment :: Test , None ) ;
255+ let ads_client = AdsClient :: new_with_mars_client ( mars_client) ;
262256
263257 let ad_placement_requests = make_happy_placement_requests ( ) ;
264258
265- let result = component . request_tile_ads ( ad_placement_requests, None ) ;
259+ let result = ads_client . request_tile_ads ( ad_placement_requests, None ) ;
266260
267261 assert ! ( result. is_ok( ) ) ;
268262 }
263+
264+ #[ test]
265+ fn test_record_click_invalidates_cache ( ) {
266+ viaduct_dev:: init_backend_dev ( ) ;
267+ let cache = HttpCache :: builder ( "test_record_click_invalidates_cache" )
268+ . build ( )
269+ . unwrap ( ) ;
270+ let mars_client = MARSClient :: new ( Environment :: Test , Some ( cache) ) ;
271+ let ads_client = AdsClient :: new_with_mars_client ( mars_client) ;
272+
273+ let response = get_example_happy_image_response ( ) ;
274+
275+ let _m1 = mockito:: mock ( "POST" , "/ads" )
276+ . with_status ( 200 )
277+ . with_header ( "content-type" , "application/json" )
278+ . with_body ( serde_json:: to_string ( & response) . unwrap ( ) )
279+ . expect ( 2 ) // we expect 2 requests to the server, one for the initial ad request and one after for the cache invalidation request
280+ . create ( ) ;
281+
282+ let response = ads_client
283+ . request_image_ads ( make_happy_placement_requests ( ) , None )
284+ . unwrap ( ) ;
285+ let callback_url = response. values ( ) . next ( ) . unwrap ( ) . callbacks . click . clone ( ) ;
286+
287+ let _m2 = mockito:: mock ( "GET" , callback_url. path ( ) )
288+ . with_status ( 200 )
289+ . create ( ) ;
290+
291+ // Doing another request should hit the cache
292+ ads_client
293+ . request_image_ads ( make_happy_placement_requests ( ) , None )
294+ . unwrap ( ) ;
295+
296+ ads_client. record_click ( callback_url) . unwrap ( ) ;
297+
298+ ads_client
299+ . request_ads :: < AdImage > (
300+ make_happy_placement_requests ( ) ,
301+ Some ( RequestCachePolicy :: default ( ) ) ,
302+ )
303+ . unwrap ( ) ;
304+ }
269305}
0 commit comments