1616package com .adobe .cq .commerce .core .components .internal .models .v1 .productlist ;
1717
1818import java .io .IOException ;
19+ import java .lang .reflect .Proxy ;
1920import java .text .NumberFormat ;
2021import java .util .Collection ;
2122import java .util .Currency ;
2223import java .util .List ;
2324import java .util .Locale ;
2425import java .util .Map ;
2526import java .util .Optional ;
27+ import java .util .function .Consumer ;
2628import java .util .stream .Collectors ;
2729
30+ import org .apache .commons .lang3 .tuple .Pair ;
2831import org .apache .http .HttpStatus ;
2932import org .apache .http .impl .client .CloseableHttpClient ;
3033import org .apache .http .osgi .services .HttpClientBuilderFactory ;
5861import com .adobe .cq .commerce .core .search .internal .services .SearchFilterServiceImpl ;
5962import com .adobe .cq .commerce .core .search .internal .services .SearchResultsServiceImpl ;
6063import com .adobe .cq .commerce .core .search .models .SearchAggregation ;
64+ import com .adobe .cq .commerce .core .search .models .SearchAggregationOption ;
65+ import com .adobe .cq .commerce .core .search .models .SearchOptions ;
6166import com .adobe .cq .commerce .core .search .models .SearchResultsSet ;
6267import com .adobe .cq .commerce .core .search .models .Sorter ;
6368import com .adobe .cq .commerce .core .search .models .SorterKey ;
69+ import com .adobe .cq .commerce .core .search .services .SearchResultsService ;
6470import com .adobe .cq .commerce .core .testing .Utils ;
6571import com .adobe .cq .commerce .graphql .client .GraphqlClient ;
6672import com .adobe .cq .commerce .graphql .client .GraphqlRequest ;
6773import com .adobe .cq .commerce .graphql .client .impl .GraphqlClientImpl ;
74+ import com .adobe .cq .commerce .magento .graphql .CategoryInterface ;
6875import com .adobe .cq .commerce .magento .graphql .CategoryTree ;
6976import com .adobe .cq .commerce .magento .graphql .GroupedProduct ;
7077import com .adobe .cq .commerce .magento .graphql .ProductImage ;
7178import com .adobe .cq .commerce .magento .graphql .ProductInterface ;
79+ import com .adobe .cq .commerce .magento .graphql .ProductInterfaceQuery ;
7280import com .adobe .cq .commerce .magento .graphql .Products ;
7381import com .adobe .cq .commerce .magento .graphql .Query ;
7482import com .adobe .cq .commerce .magento .graphql .gson .QueryDeserializer ;
@@ -305,12 +313,103 @@ public void getProducts() {
305313
306314 SearchResultsSet searchResultsSet = productListModel .getSearchResultsSet ();
307315 List <SearchAggregation > searchAggregations = searchResultsSet .getSearchAggregations ();
308- Assert .assertEquals (7 , searchAggregations .size ());
316+ Assert .assertEquals (8 , searchAggregations .size ());
309317
310- // We want to make sure the category_id aggregation is not present
311- Optional <SearchAggregation > categoryIdAggregation = searchAggregations .stream ().filter (a -> a .getIdentifier ().equals ("category_id" ))
318+ // check category aggregation
319+ Optional <SearchAggregation > categoryIdAggregation = searchAggregations .stream ().filter (a -> a .getIdentifier ().equals (
320+ ProductListImpl .CATEGORY_AGGREGATION_ID ))
312321 .findAny ();
313- Assert .assertFalse (categoryIdAggregation .isPresent ());
322+ Assert .assertTrue (categoryIdAggregation .isPresent ());
323+ List <SearchAggregationOption > options = categoryIdAggregation .get ().getOptions ();
324+ Assert .assertEquals (2 , options .size ());
325+
326+ SearchAggregationOption opt = options .get (0 );
327+ Assert .assertEquals ("3" , opt .getFilterValue ());
328+ Assert .assertEquals ("Gear" , opt .getDisplayLabel ());
329+ Assert .assertEquals ("/content/pageA.html/running/gear.html" , opt .getPageUrl ());
330+
331+ opt = options .get (1 );
332+ Assert .assertEquals ("4" , opt .getFilterValue ());
333+ Assert .assertEquals ("Bags" , opt .getDisplayLabel ());
334+ Assert .assertEquals ("/content/pageA.html/running/bags.html" , opt .getPageUrl ());
335+
336+ // We want to make sure all price ranges are properly processed
337+ SearchAggregation priceAggregation = searchAggregations .stream ().filter (a -> a .getIdentifier ().equals ("price" )).findFirst ().get ();
338+ Assert .assertEquals (3 , priceAggregation .getOptions ().size ());
339+ Assert .assertEquals (3 , priceAggregation .getOptionCount ());
340+ Assert .assertTrue (priceAggregation .getOptions ().stream ().anyMatch (o -> o .getDisplayLabel ().equals ("30-40" )));
341+ Assert .assertTrue (priceAggregation .getOptions ().stream ().anyMatch (o -> o .getDisplayLabel ().equals ("40-*" )));
342+ Assert .assertTrue (priceAggregation .getOptions ().stream ().anyMatch (o -> o .getDisplayLabel ().equals ("14" )));
343+ }
344+
345+ // custom marker interface for search aggregation options
346+ private interface MySearchAggregationOption {};
347+
348+ @ Test
349+ public void getProductsWithCustomAggregationOptions () {
350+ adaptToProductList ();
351+
352+ // inject custom search results service which returns custom search aggregation objects
353+ SearchResultsService searchResultsService = (SearchResultsService ) Whitebox .getInternalState (productListModel ,
354+ "searchResultsService" );
355+ ClassLoader classLoader = getClass ().getClassLoader ();
356+ Whitebox .setInternalState (productListModel , "searchResultsService" , Proxy .newProxyInstance (classLoader ,
357+ new Class [] { SearchResultsService .class }, (proxy , method , args ) -> {
358+ if (method .getName ().equals ("performSearch" ) && method .getParameterCount () == 6 ) {
359+ Pair <CategoryInterface , SearchResultsSet > pair = searchResultsService .performSearch (
360+ (SearchOptions ) args [0 ],
361+ (Resource ) args [1 ],
362+ (Page ) args [2 ],
363+ (SlingHttpServletRequest ) args [3 ],
364+ (Consumer <ProductInterfaceQuery >) args [4 ],
365+ (AbstractCategoryRetriever ) args [5 ]);
366+
367+ Class [] optionInterfaces = { SearchAggregationOption .class , MySearchAggregationOption .class };
368+ for (SearchAggregation aggregation : pair .getRight ().getSearchAggregations ()) {
369+ List <SearchAggregationOption > options = aggregation .getOptions ();
370+ List <SearchAggregationOption > myOptions = options .stream ().map (
371+ o -> (SearchAggregationOption ) Proxy .newProxyInstance (classLoader , optionInterfaces ,
372+ (oProxy , oMethod , oArgs ) -> oMethod .invoke (o , oArgs ))).collect (Collectors .toList ());
373+ options .clear ();
374+ options .addAll (myOptions );
375+ }
376+
377+ return pair ;
378+ } else {
379+ return method .invoke (searchResultsService , args );
380+ }
381+ }));
382+
383+ Collection <ProductListItem > products = productListModel .getProducts ();
384+ Assert .assertNotNull (products );
385+
386+ // We introduce one "faulty" product data in the response, it should be skipped
387+ Assert .assertEquals (4 , products .size ());
388+
389+ SearchResultsSet searchResultsSet = productListModel .getSearchResultsSet ();
390+ List <SearchAggregation > searchAggregations = searchResultsSet .getSearchAggregations ();
391+ Assert .assertEquals (8 , searchAggregations .size ());
392+
393+ // check category aggregation
394+ Optional <SearchAggregation > categoryIdAggregation = searchAggregations .stream ().filter (a -> a .getIdentifier ().equals (
395+ ProductListImpl .CATEGORY_AGGREGATION_ID ))
396+ .findAny ();
397+ Assert .assertTrue (categoryIdAggregation .isPresent ());
398+ List <SearchAggregationOption > options = categoryIdAggregation .get ().getOptions ();
399+ Assert .assertEquals (2 , options .size ());
400+
401+ SearchAggregationOption opt = options .get (0 );
402+ // for category aggregation custom options are replaced
403+ Assert .assertFalse (opt instanceof MySearchAggregationOption );
404+ Assert .assertEquals ("3" , opt .getFilterValue ());
405+ Assert .assertEquals ("Gear" , opt .getDisplayLabel ());
406+ Assert .assertEquals ("/content/pageA.html/running/gear.html" , opt .getPageUrl ());
407+
408+ opt = options .get (1 );
409+ Assert .assertFalse (opt instanceof MySearchAggregationOption );
410+ Assert .assertEquals ("4" , opt .getFilterValue ());
411+ Assert .assertEquals ("Bags" , opt .getDisplayLabel ());
412+ Assert .assertEquals ("/content/pageA.html/running/bags.html" , opt .getPageUrl ());
314413
315414 // We want to make sure all price ranges are properly processed
316415 SearchAggregation priceAggregation = searchAggregations .stream ().filter (a -> a .getIdentifier ().equals ("price" )).findFirst ().get ();
@@ -319,6 +418,9 @@ public void getProducts() {
319418 Assert .assertTrue (priceAggregation .getOptions ().stream ().anyMatch (o -> o .getDisplayLabel ().equals ("30-40" )));
320419 Assert .assertTrue (priceAggregation .getOptions ().stream ().anyMatch (o -> o .getDisplayLabel ().equals ("40-*" )));
321420 Assert .assertTrue (priceAggregation .getOptions ().stream ().anyMatch (o -> o .getDisplayLabel ().equals ("14" )));
421+
422+ // for other aggregations custom options are preserved
423+ Assert .assertTrue (priceAggregation .getOptions ().get (0 ) instanceof MySearchAggregationOption );
322424 }
323425
324426 @ Test
0 commit comments