2424import org .prebid .server .proto .openrtb .ext .ExtPrebid ;
2525import org .prebid .server .proto .openrtb .ext .request .thetradedesk .ExtImpTheTradeDesk ;
2626
27+ import java .math .BigDecimal ;
2728import java .util .Arrays ;
2829import java .util .List ;
2930import java .util .Set ;
3334import static java .util .function .UnaryOperator .identity ;
3435import static org .assertj .core .api .Assertions .assertThat ;
3536import static org .assertj .core .api .Assertions .assertThatIllegalArgumentException ;
37+ import static org .assertj .core .api .Assertions .tuple ;
3638import static org .prebid .server .proto .openrtb .ext .response .BidType .banner ;
3739import static org .prebid .server .proto .openrtb .ext .response .BidType .video ;
3840import static org .prebid .server .proto .openrtb .ext .response .BidType .xNative ;
@@ -458,7 +460,7 @@ public void makeBidsShouldReturnVideoBid() throws JsonProcessingException {
458460 }
459461
460462 @ Test
461- public void makeBidsShouldThrowErrorWhenMediaTypeIsMissing () throws JsonProcessingException {
463+ public void makeBidsShouldReturnErrorWhenMediaTypeIsMissing () throws JsonProcessingException {
462464 // given
463465 final BidderCall <BidRequest > httpCall = givenHttpCall (
464466 givenBidResponse (bidBuilder -> bidBuilder .impid ("123" )));
@@ -469,7 +471,234 @@ public void makeBidsShouldThrowErrorWhenMediaTypeIsMissing() throws JsonProcessi
469471 // then
470472 assertThat (result .getValue ()).isEmpty ();
471473 assertThat (result .getErrors ()).hasSize (1 )
472- .containsOnly (BidderError .badServerResponse ("unsupported mtype: null" ));
474+ .containsOnly (BidderError .badServerResponse ("could not define media type for impression: 123" ));
475+ }
476+
477+ @ Test
478+ public void makeBidsShouldReturnValidBidsAndErrorsForMixedMediaTypes () throws JsonProcessingException {
479+ // given
480+ final BidderCall <BidRequest > httpCall = givenHttpCall (
481+ mapper .writeValueAsString (BidResponse .builder ()
482+ .cur ("USD" )
483+ .seatbid (singletonList (SeatBid .builder ()
484+ .bid (Arrays .asList (
485+ Bid .builder ().mtype (1 ).impid ("valid1" ).build (), // valid banner
486+ Bid .builder ().mtype (3 ).impid ("invalid1" ).build (), // invalid mtype
487+ Bid .builder ().mtype (2 ).impid ("valid2" ).build (), // valid video
488+ Bid .builder ().mtype (null ).impid ("invalid2" ).build () // null mtype
489+ ))
490+ .build ()))
491+ .build ()));
492+
493+ // when
494+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
495+
496+ // then
497+ assertThat (result .getValue ()).hasSize (2 )
498+ .extracting (bidderBid -> bidderBid .getBid ().getImpid ())
499+ .containsExactly ("valid1" , "valid2" );
500+ assertThat (result .getErrors ()).hasSize (2 )
501+ .containsExactly (
502+ BidderError .badServerResponse ("could not define media type for impression: invalid1" ),
503+ BidderError .badServerResponse ("could not define media type for impression: invalid2" ));
504+ }
505+
506+ @ Test
507+ public void makeBidsShouldReplacePriceMacroInNurlAndAdmWithBidPrice () throws JsonProcessingException {
508+ // given
509+ final BidderCall <BidRequest > httpCall = givenHttpCall (
510+ givenBidResponse (bidBuilder -> bidBuilder
511+ .mtype (1 )
512+ .impid ("123" )
513+ .price (BigDecimal .valueOf (1.23 ))
514+ .nurl ("http://example.com/nurl?price=${AUCTION_PRICE}" )
515+ .adm ("<div>Price: ${AUCTION_PRICE}</div>" )));
516+
517+ // when
518+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
519+
520+ // then
521+ assertThat (result .getErrors ()).isEmpty ();
522+ assertThat (result .getValue ()).hasSize (1 )
523+ .extracting (BidderBid ::getBid )
524+ .extracting (Bid ::getNurl , Bid ::getAdm , Bid ::getPrice )
525+ .containsOnly (tuple ("http://example.com/nurl?price=1.23" , "<div>Price: 1.23</div>" , BigDecimal .valueOf (1.23 )));
526+ }
527+
528+ @ Test
529+ public void makeBidsShouldReplacePriceMacroWithZeroWhenBidPriceIsNull () throws JsonProcessingException {
530+ // given
531+ final BidderCall <BidRequest > httpCall = givenHttpCall (
532+ givenBidResponse (bidBuilder -> bidBuilder
533+ .mtype (1 )
534+ .impid ("123" )
535+ .price (null )
536+ .nurl ("http://example.com/nurl?price=${AUCTION_PRICE}" )
537+ .adm ("<div>Price: ${AUCTION_PRICE}</div>" )));
538+
539+ // when
540+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
541+
542+ // then
543+ assertThat (result .getErrors ()).isEmpty ();
544+ assertThat (result .getValue ()).hasSize (1 )
545+ .extracting (BidderBid ::getBid )
546+ .extracting (Bid ::getNurl , Bid ::getAdm )
547+ .containsOnly (tuple ("http://example.com/nurl?price=0" , "<div>Price: 0</div>" ));
548+ }
549+
550+ @ Test
551+ public void makeBidsShouldReplacePriceMacroWithZeroWhenBidPriceIsZero () throws JsonProcessingException {
552+ // given
553+ final BidderCall <BidRequest > httpCall = givenHttpCall (
554+ givenBidResponse (bidBuilder -> bidBuilder
555+ .mtype (1 )
556+ .impid ("123" )
557+ .price (BigDecimal .ZERO )
558+ .nurl ("http://example.com/nurl?price=${AUCTION_PRICE}" )
559+ .adm ("<div>Price: ${AUCTION_PRICE}</div>" )));
560+
561+ // when
562+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
563+
564+ // then
565+ assertThat (result .getErrors ()).isEmpty ();
566+ assertThat (result .getValue ()).hasSize (1 )
567+ .extracting (BidderBid ::getBid )
568+ .extracting (Bid ::getNurl , Bid ::getAdm )
569+ .containsOnly (tuple ("http://example.com/nurl?price=0" , "<div>Price: 0</div>" ));
570+ }
571+
572+ @ Test
573+ public void makeBidsShouldReplacePriceMacroInNurlOnlyWhenAdmDoesNotContainMacro () throws JsonProcessingException {
574+ // given
575+ final BidderCall <BidRequest > httpCall = givenHttpCall (
576+ givenBidResponse (bidBuilder -> bidBuilder
577+ .mtype (1 )
578+ .impid ("123" )
579+ .price (BigDecimal .valueOf (5.67 ))
580+ .nurl ("http://example.com/nurl?price=${AUCTION_PRICE}" )
581+ .adm ("<div>No macro here</div>" )));
582+
583+ // when
584+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
585+
586+ // then
587+ assertThat (result .getErrors ()).isEmpty ();
588+ assertThat (result .getValue ()).hasSize (1 )
589+ .extracting (BidderBid ::getBid )
590+ .extracting (Bid ::getNurl , Bid ::getAdm )
591+ .containsOnly (tuple ("http://example.com/nurl?price=5.67" , "<div>No macro here</div>" ));
592+ }
593+
594+ @ Test
595+ public void makeBidsShouldReplacePriceMacroInAdmOnlyWhenNurlDoesNotContainMacro () throws JsonProcessingException {
596+ // given
597+ final BidderCall <BidRequest > httpCall = givenHttpCall (
598+ givenBidResponse (bidBuilder -> bidBuilder
599+ .mtype (1 )
600+ .impid ("123" )
601+ .price (BigDecimal .valueOf (8.90 ))
602+ .nurl ("http://example.com/nurl" )
603+ .adm ("<div>Price: ${AUCTION_PRICE}</div>" )));
604+
605+ // when
606+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
607+
608+ // then
609+ assertThat (result .getErrors ()).isEmpty ();
610+ assertThat (result .getValue ()).hasSize (1 )
611+ .extracting (BidderBid ::getBid )
612+ .extracting (Bid ::getNurl , Bid ::getAdm )
613+ .containsOnly (tuple ("http://example.com/nurl" , "<div>Price: 8.9</div>" ));
614+ }
615+
616+ @ Test
617+ public void makeBidsShouldNotReplacePriceMacroWhenNurlAndAdmDoNotContainMacro () throws JsonProcessingException {
618+ // given
619+ final BidderCall <BidRequest > httpCall = givenHttpCall (
620+ givenBidResponse (bidBuilder -> bidBuilder
621+ .mtype (1 )
622+ .impid ("123" )
623+ .price (BigDecimal .valueOf (12.34 ))
624+ .nurl ("http://example.com/nurl" )
625+ .adm ("<div>No macro</div>" )));
626+
627+ // when
628+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
629+
630+ // then
631+ assertThat (result .getErrors ()).isEmpty ();
632+ assertThat (result .getValue ()).hasSize (1 )
633+ .extracting (BidderBid ::getBid )
634+ .extracting (Bid ::getNurl , Bid ::getAdm )
635+ .containsOnly (tuple ("http://example.com/nurl" , "<div>No macro</div>" ));
636+ }
637+
638+ @ Test
639+ public void makeBidsShouldHandleNullNurlAndAdm () throws JsonProcessingException {
640+ // given
641+ final BidderCall <BidRequest > httpCall = givenHttpCall (
642+ givenBidResponse (bidBuilder -> bidBuilder
643+ .mtype (1 )
644+ .impid ("123" )
645+ .price (BigDecimal .valueOf (15.00 ))
646+ .nurl (null )
647+ .adm (null )));
648+
649+ // when
650+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
651+
652+ // then
653+ assertThat (result .getErrors ()).isEmpty ();
654+ assertThat (result .getValue ()).hasSize (1 )
655+ .extracting (BidderBid ::getBid )
656+ .extracting (Bid ::getNurl , Bid ::getAdm )
657+ .containsOnly (tuple (null , null ));
658+ }
659+
660+ @ Test
661+ public void makeBidsShouldReplaceMultiplePriceMacrosInSameField () throws JsonProcessingException {
662+ // given
663+ final BidderCall <BidRequest > httpCall = givenHttpCall (
664+ givenBidResponse (bidBuilder -> bidBuilder
665+ .mtype (1 )
666+ .impid ("123" )
667+ .price (BigDecimal .valueOf (9.99 ))
668+ .nurl ("http://example.com/nurl?price=${AUCTION_PRICE}&backup_price=${AUCTION_PRICE}" )
669+ .adm ("<div>Price: ${AUCTION_PRICE}, Fallback: ${AUCTION_PRICE}</div>" )));
670+
671+ // when
672+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
673+
674+ // then
675+ assertThat (result .getErrors ()).isEmpty ();
676+ assertThat (result .getValue ()).hasSize (1 )
677+ .extracting (BidderBid ::getBid )
678+ .extracting (Bid ::getNurl , Bid ::getAdm )
679+ .containsOnly (tuple ("http://example.com/nurl?price=9.99&backup_price=9.99" , "<div>Price: 9.99, Fallback: 9.99</div>" ));
680+ }
681+
682+ @ Test
683+ public void makeBidsShouldHandleLargeDecimalPrices () throws JsonProcessingException {
684+ // given
685+ final BidderCall <BidRequest > httpCall = givenHttpCall (
686+ givenBidResponse (bidBuilder -> bidBuilder
687+ .mtype (1 )
688+ .impid ("123" )
689+ .price (new BigDecimal ("123456789.123456789" ))
690+ .nurl ("http://example.com/nurl?price=${AUCTION_PRICE}" )
691+ .adm ("<div>Price: ${AUCTION_PRICE}</div>" )));
692+
693+ // when
694+ final Result <List <BidderBid >> result = target .makeBids (httpCall , null );
695+
696+ // then
697+ assertThat (result .getErrors ()).isEmpty ();
698+ assertThat (result .getValue ()).hasSize (1 )
699+ .extracting (BidderBid ::getBid )
700+ .extracting (Bid ::getNurl , Bid ::getAdm )
701+ .containsOnly (tuple ("http://example.com/nurl?price=123456789.123456789" , "<div>Price: 123456789.123456789</div>" ));
473702 }
474703
475704 private String givenBidResponse (UnaryOperator <Bid .BidBuilder > bidCustomizer ) throws JsonProcessingException {
@@ -508,5 +737,4 @@ private static ObjectNode impExt(String publisherId) {
508737 private static ObjectNode impExt (String publisherId , String supplySourceId ) {
509738 return mapper .valueToTree (ExtPrebid .of (null , ExtImpTheTradeDesk .of (publisherId , supplySourceId )));
510739 }
511-
512740}
0 commit comments