From 598fc43371f81b13175bdaff7ce603441bcdf684 Mon Sep 17 00:00:00 2001 From: Ignas Traskevicius Date: Tue, 21 May 2024 13:00:59 +0300 Subject: [PATCH] release v3.2.0 --- .gitignore | 1 + CHANGELOG.md | 5 + pom.xml | 271 --- sdk-core/.DS_Store | Bin 6148 -> 0 bytes .../build-tools/checkstyle/checkstyle.xml | 33 +- sdk-core/pom.xml | 7 +- .../sdk/SdkInternalConfiguration.java | 12 - .../ci/markets/MarketDescriptionCi.java | 56 +- .../caching/ci/markets/MarketOutcomeCi.java | 2 +- .../ci/markets/VariantDescriptionCi.java | 27 + .../exportable/ExportableCompleteRoundCi.java | 1 - .../InvariantMarketDescriptionCache.java | 7 +- .../markets/MarketDescriptionCache.java | 5 +- .../MarketDescriptionProviderImpl.java | 14 +- .../VariantMarketDescriptionCache.java | 189 +- .../unifiedodds/sdk/di/CachingModule.java | 6 +- .../unifiedodds/sdk/di/HttpClientModule.java | 7 +- .../sdk/domain/language/Languages.java | 27 + .../sdk/domain/language/Translations.java | 5 +- .../sdk/impl/ChannelMessageConsumer.java | 7 + .../sdk/impl/ChannelMessageConsumerImpl.java | 6 + .../unifiedodds/sdk/impl/DataWrapper.java | 2 +- .../sdk/impl/HttpResponseHandler.java | 9 +- .../apireaders/MessageAndActionExtractor.java | 67 +- .../sdk/impl/apireaders/WhoAmIReader.java | 5 +- .../impl/entities/BasicTournamentImpl.java | 2 +- .../sdk/impl/entities/DrawImpl.java | 7 +- .../sdk/impl/entities/LotteryImpl.java | 8 +- .../sdk/impl/entities/MatchImpl.java | 2 +- .../sdk/impl/entities/StageImpl.java | 2 +- .../sdk/impl/entities/TournamentImpl.java | 4 +- .../impl/markets/MarketDescriptionImpl.java | 6 +- .../sdk/impl/markets/MarketManagerImpl.java | 1 - .../sdk/impl/markets/NameProviderImpl.java | 9 +- .../sportradar/utils/thread/sleep/Sleep.java | 1 + .../javadoc/unified-odds-overview.html | 1 - .../unifiedodds/sdk/BookingManagerTest.java | 2 +- .../sdk/ConfigureMtsSpecificTest.java | 14 - .../unifiedodds/sdk/CustomBetIT.java | 103 ++ ...ageAsDiIsTestedAtIntegrationLevelTest.java | 11 +- .../unifiedodds/sdk/ThreadInterruptTest.java | 54 +- .../caching/markets/DataProviderAnswers.java | 21 + .../InvariantMarketDescriptionCacheTest.java | 375 ++++ .../InvariantMarketDescriptionCaches.java | 44 + ...ons.java => MarketDescriptionFactory.java} | 15 +- .../MarketDescriptionProviderImplTest.java | 293 +++ .../markets/MarketDescriptionProviders.java | 79 + .../markets/VariantDescriptionCaches.java | 42 + .../VariantMarketDescriptionCacheTest.java | 1602 +++++++++++++++++ ...rketDescriptionCacheUsingOutcomesTest.java | 434 +++++ .../VariantMarketDescriptionCaches.java | 58 + .../sdk/cfg/SdkConfigurationReaderStubs.java | 67 + .../sdk/cfg/UofConfigurationTests.java | 197 +- .../sdk/cfg/UofConfigurations.java | 201 +++ .../unifiedodds/sdk/conn/ApiSimulator.java | 164 +- .../sdk/conn/FlexScoreMarketIT.java | 182 ++ .../unifiedodds/sdk/conn/GlobalVariables.java | 25 +- .../unifiedodds/sdk/conn/Identifiable.java | 8 + .../unifiedodds/sdk/conn/MarketVariant.java | 60 + .../conn/MarketWithFaultyDescriptionIT.java | 381 +++- .../sdk/conn/MarketWithMissingNameIT.java | 332 ++++ .../conn/MarketWithMissingOutcomeNameIT.java | 541 ++++++ .../sdk/conn/MessagingSimulator.java | 79 + .../unifiedodds/sdk/conn/RabbitProducer.java | 3 +- .../unifiedodds/sdk/conn/RoutingKeys.java | 20 +- .../sdk/conn/SapiBookmakerDetails.java | 23 + .../sdk/conn/SapiMarketDescriptions.java | 1561 +++++++++++++++- .../sdk/conn/SapiVariantDescriptions.java | 84 + .../unifiedodds/sdk/conn/SdkConnectionIT.java | 6 +- .../unifiedodds/sdk/conn/SdkSetup.java | 15 + .../sdk/conn/SpecifierDescriptions.java | 34 + .../unifiedodds/sdk/conn/Sport.java | 3 +- .../unifiedodds/sdk/conn/UfMarkets.java | 80 +- .../sdk/conn/WaiterForSingleMessage.java | 18 + .../ChampionshipFreeTextMarketIds.java | 50 + .../conn/marketids/ExactGoalsMarketIds.java | 40 + .../conn/marketids/FlexScoreMarketIds.java | 447 +++++ .../sdk/conn/marketids/FreeTextMarketIds.java | 103 ++ .../sdk/conn/marketids/Markets.java | 25 + .../OddEvenMarketIds.java} | 18 +- .../sdk/conn/marketids/OneXtwoMarketIds.java | 17 + .../language/LanguagesBestEffortTest.java | 61 + .../impl/BookmakerDetailsDataProvider.java | 30 + .../sdk/impl/ChannelMessageConsumerTest.java | 49 +- .../sdk/impl/HttpDataFetcherIT.java | 4 +- .../sdk/impl/HttpDataFetcherLoggersIT.java | 4 +- .../sdk/impl/HttpResponseHandlerTest.java | 48 +- .../impl/MarketDescriptionDataProviders.java | 118 ++ .../impl/VariantDescriptionDataProviders.java | 31 + .../sdk/impl/apireaders/HttpHelperIT.java | 2 +- .../MessageAndActionExtractorTest.java | 118 +- .../impl/entities/BasicTournamentAssert.java | 33 + .../entities/BasicTournamentImplTest.java | 77 +- .../sdk/impl/entities/DrawAssert.java | 33 + .../sdk/impl/entities/DrawImplTest.java | 92 + .../sdk/impl/entities/LotteryAssert.java | 33 + .../sdk/impl/entities/LotteryImplTest.java | 95 + .../sdk/impl/entities/MatchImplTest.java | 68 + .../sdk/impl/entities/StageAssert.java | 33 + .../sdk/impl/entities/StageImplTest.java | 97 + .../sdk/impl/entities/TournamentAssert.java | 34 + .../sdk/impl/entities/TournamentImplTest.java | 92 +- .../impl/markets/NameProviderImplTest.java | 42 +- .../NameProviderStatusMessageTest.java | 4 +- .../oddsentities/markets/MarketAssert.java | 10 +- .../markets/MarketDefinitionAssert.java | 2 + .../markets/MarketFactoryImplCancelTest.java | 51 +- .../markets/MarketFactoryImplSimpleTest.java | 43 +- .../MarketFactoryImplWithOddsTest.java | 56 +- ...arketFactoryImplWithProbabilitiesTest.java | 255 +-- .../MarketFactoryImplWithSettlementTest.java | 49 +- .../oddsentities/markets/OutcomeAssert.java | 44 + .../ConnectionFixtureTest.java | 8 +- .../RabbitMqChannelImplTest.java | 13 +- .../impl/recovery/RecoveryManagerTests.java | 8 +- ...ScheduledSportEventIdsForTournamentIT.java | 2 +- .../sdk/shared/FeedMessageBuilder.java | 21 +- .../generic/naturallanguage/Conjunctions.java | 11 + .../generic/naturallanguage/Determiners.java | 11 + .../generic/naturallanguage/Prepositions.java | 31 + .../utils/domain/config/Tokens.java | 15 + .../domain/specifiers/MarketSpecifiers.java | 8 +- .../utils/generic/testing/RandomInteger.java | 4 + .../generic/testing/RandomIntegerTest.java | 17 + .../generic/testing/RandomObjectPicker.java | 17 +- sdk-example/checkstyle/checkstyle.xml | 8 + sdk-example/pom.xml | 77 +- .../example/BasicUofSdkExampleMain.java | 8 +- .../unifiedodds/example/UofSdkExamples.java | 10 +- .../example/examples/ReplaySessionSetup.java | 12 +- .../example/examples/SingleSessionSetup.java | 15 +- .../UFSdkConfiguration.properties.all | 43 +- .../src/main/resources/application.yml.all | 3 +- 133 files changed, 9643 insertions(+), 1152 deletions(-) delete mode 100644 pom.xml delete mode 100644 sdk-core/.DS_Store create mode 100644 sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/domain/language/Languages.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/CustomBetIT.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/DataProviderAnswers.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCacheTest.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCaches.java rename sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/{MarketDescriptions.java => MarketDescriptionFactory.java} (91%) create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviderImplTest.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviders.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantDescriptionCaches.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCacheTest.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCacheUsingOutcomesTest.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCaches.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/SdkConfigurationReaderStubs.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/UofConfigurations.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/FlexScoreMarketIT.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/Identifiable.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketVariant.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithMissingNameIT.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithMissingOutcomeNameIT.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MessagingSimulator.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiBookmakerDetails.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiVariantDescriptions.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SpecifierDescriptions.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/ChampionshipFreeTextMarketIds.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/ExactGoalsMarketIds.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/FlexScoreMarketIds.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/FreeTextMarketIds.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/Markets.java rename sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/{OddEvenMarket.java => marketids/OddEvenMarketIds.java} (69%) create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/OneXtwoMarketIds.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/domain/language/LanguagesBestEffortTest.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/BookmakerDetailsDataProvider.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/MarketDescriptionDataProviders.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/VariantDescriptionDataProviders.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentAssert.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawAssert.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawImplTest.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryAssert.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryImplTest.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/StageAssert.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/StageImplTest.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentAssert.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Conjunctions.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Determiners.java create mode 100644 sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Prepositions.java create mode 100644 sdk-core/src/test/java/com/sportradar/utils/domain/config/Tokens.java create mode 100644 sdk-example/checkstyle/checkstyle.xml diff --git a/.gitignore b/.gitignore index 6a183b37..d0feeb33 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,4 @@ application.yml # logs *.log.gz +/local-environment/uf-api-proxy/node_modules/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 2635bf37..cdeb8108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ### Unified Feed SDK 3.x changelog +**3.2.0 (2024-05-15)** +* feat: Improved Variant Market handling when API call is not successful or has some faulty data (i.e. missing outcome name or missing market name) +* fix: CustomBet API error responses are properly extracted and available for analysis either in logs or in the exception message (dependent on the configured exception handling strategy) +* fix: Environment.GlobalReplay works properly with production token. API host is updated to production during the bookmaker discovery call. + **3.1.0 (2024-02-19)** * SDK can be used in a construct close-with-resources * recovering after network outages no longer causes message duplication diff --git a/pom.xml b/pom.xml deleted file mode 100644 index d446cbed..00000000 --- a/pom.xml +++ /dev/null @@ -1,271 +0,0 @@ - - - - - - 4.0.0 - com.sportradar.unifiedodds.sdk - unified-feed-sdk-parent - 3.1.0 - pom - Unified Odds Feed SDK - parent - UnifiedFeed SDK is a client library that enables easier integration with the Betradar XML feeds. SDK exposes XML feed service interface in a more user-friendly way and isolates the client from having to do XML feed parsing, proper connection handling, error recovery, event queuing, data caching and dispatching. It also makes a client solution more stable and robust when it comes to feed handling, especially with the release of new and updated XML feed versions. - 2016 - - - https://github.com/sportradar/UnifiedOddsSdkJava - - - - - - ${project.build.directory}/endorsed - UTF-8 - - 1.0.69 - 3.6.0 - 4.5.14 - 2.0.7 - 1.3.8 - 4.12 - 1.1.1 - 2.2 - 4.5.1 - 31.1-jre - 4.0 - 57.1 - 1.31 - 5.3.9 - - - - - - org.slf4j - slf4j-api - ${slf4j.version} - - - ch.qos.logback - logback-core - ${logback.version} - - - ch.qos.logback - logback-classic - ${logback.version} - - - junit - junit - ${junit.version} - test - - - org.hamcrest - hamcrest - ${hamcrest.version} - test - - - org.mockito - mockito-core - ${mockito.version} - test - - - org.mockito - mockito-inline - ${mockito.version} - test - - - org.projectlombok - lombok - 1.18.24 - provided - - - com.google.guava - guava - ${guava.version} - - - com.rabbitmq - amqp-client - ${rabbitMQ.version} - - - pl.pragmatists - JUnitParams - ${junitparams.version} - test - - - - - - - org.slf4j - slf4j-api - - - ch.qos.logback - logback-core - - - ch.qos.logback - logback-classic - - - org.projectlombok - lombok - - - junit - junit - - - org.hamcrest - hamcrest - - - org.mockito - mockito-core - - - org.mockito - mockito-inline - - - - - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - org.apache.maven.plugins - maven-deploy-plugin - 2.8.2 - - true - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.3 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - - org.codehaus.mojo - versions-maven-plugin - 2.4 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.2.0 - - - com.puppycrawl.tools - checkstyle - 9.3 - - - - true - sdk-core/build-tools/checkstyle/checkstyle.xml - UTF-8 - **/*.properties,**/*.xml - true - - - - test - - check - - - - - - org.jacoco - jacoco-maven-plugin - 0.8.8 - - - - prepare-agent - - - - report - prepare-package - - report - - - - XML - HTML - - - - - - - com.hubspot.maven.plugins - prettier-maven-plugin - 0.18 - - 1.6.1 - 110 - 4 - false - true - true - - - - validate - - write - - - - - - - - - customer-facing-library-only - - sdk-core - - - - default - - true - - - sdk-core - sdk-example - - - - diff --git a/sdk-core/.DS_Store b/sdk-core/.DS_Store deleted file mode 100644 index 91a7914c43115676172e50edb6eca094365f09c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKI|>3p3{6x-u(7n9D|mxJ^aNhOFE)bUr{BtRc{E=>2(sD;Hu3_=o5|$Ovai@| zL`0|8?L=fGA_KUg+$^-s&dpoa$&3QwxML@m?Rj%pACHGt_4|Zz`*M+$oMit6|NX7Z zQUNMJ1*iZOpaP#1u-*$B3z>WZy# WUK5)@rz7ulAb$o-7aA4#wE_?MTosrA diff --git a/sdk-core/build-tools/checkstyle/checkstyle.xml b/sdk-core/build-tools/checkstyle/checkstyle.xml index a54cc3a6..6ad575d2 100644 --- a/sdk-core/build-tools/checkstyle/checkstyle.xml +++ b/sdk-core/build-tools/checkstyle/checkstyle.xml @@ -34,6 +34,14 @@ + + + + + + + + @@ -223,6 +231,10 @@ + + + + @@ -331,6 +343,10 @@ + + + + @@ -1644,6 +1660,18 @@ + + + + + + + + + + + + @@ -1721,7 +1749,6 @@ - @@ -1730,7 +1757,9 @@ - + + + diff --git a/sdk-core/pom.xml b/sdk-core/pom.xml index bb482e63..275f8371 100644 --- a/sdk-core/pom.xml +++ b/sdk-core/pom.xml @@ -8,7 +8,7 @@ com.sportradar.unifiedodds.sdk unified-feed-sdk - 3.1.0 + 3.3.0-SNAPSHOT Unified Odds Feed SDK @@ -56,7 +56,8 @@ UTF-8 - + UTF-8 + 5.17.0 5.2.1 2.0.7 @@ -278,7 +279,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.4 sign-artifacts diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/SdkInternalConfiguration.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/SdkInternalConfiguration.java index 25986f25..c47ef9b1 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/SdkInternalConfiguration.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/SdkInternalConfiguration.java @@ -42,7 +42,6 @@ public class SdkInternalConfiguration { private final int httpClientTimeout; private final int recoveryHttpClientTimeout; private final List disabledProducers; - private final boolean simpleVariantCaching; private final Set schedulerTasksToSkip; private final String messagingVirtualHost; private String apiHost; @@ -103,7 +102,6 @@ public class SdkInternalConfiguration { recoveryHttpClientMaxConnPerRoute = cfg.getApi().getHttpClientMaxConnPerRoute(); cleanTrafficLogEntries = false; - simpleVariantCaching = false; schedulerTasksToSkip = new HashSet<>(); } @@ -305,15 +303,6 @@ public List getDisabledProducers() { return disabledProducers; } - /** - * Returns an indication if the variants should be cached in a more simpler manner - * - * @return true - */ - public boolean getSimpleVariantCaching() { - return simpleVariantCaching; - } - /** * Returns a set of task names that should be skipped * @@ -401,7 +390,6 @@ public String toString() { .add("recoveryHttpClientMaxConnTotal=" + recoveryHttpClientMaxConnTotal) .add("recoveryHttpClientMaxConnPerRoute=" + recoveryHttpClientMaxConnPerRoute) .add("disabledProducers=" + disabledProducers) - .add("simpleVariantCaching=" + simpleVariantCaching) .add("schedulerTasksToSkip=" + schedulerTasksToSkip) .add("messagingVirtualHost='" + messagingVirtualHost + "'") .add("apiHost='" + apiHost + "'") diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/MarketDescriptionCi.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/MarketDescriptionCi.java index 7b414017..40a45c68 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/MarketDescriptionCi.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/MarketDescriptionCi.java @@ -6,6 +6,7 @@ import static com.sportradar.unifiedodds.sdk.impl.UnifiedFeedConstants.FREETEXT_VARIANT_VALUE; import static com.sportradar.unifiedodds.sdk.impl.UnifiedFeedConstants.OUTCOMETEXT_VARIANT_VALUE; +import static java.lang.String.format; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -17,6 +18,7 @@ import com.sportradar.utils.SdkHelper; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,7 +71,12 @@ public MarketDescriptionCi( id = market.getId(); names = new ConcurrentHashMap<>(); - names.put(locale, market.getName()); + Optional + .ofNullable(market.getName()) + .filter(name -> !Strings.isNullOrEmpty(name)) + .ifPresent(name -> names.put(locale, name)); + logIfMarketIsFaulty(market, locale); + outcomeType = combineOutcomeType(market.getOutcomeType(), market.getIncludesOutcomesOfType()); variant = market.getVariant(); descriptions = new ConcurrentHashMap<>(); @@ -93,6 +100,7 @@ public MarketDescriptionCi( .stream() .map(o -> new MarketOutcomeCi(o, locale)) .collect(Collectors.toList()); + logFaultyOutcomes(locale); mappings = market.getMappings() == null @@ -132,11 +140,54 @@ public MarketDescriptionCi( this.lastDataReceived = new Date(); } + private void logFaultyOutcomes(Locale language) { + if (outcomes != null) { + outcomes + .stream() + .filter(hasMissingName(language)) + .forEach(o -> + logger.warn( + format( + "Cached market description without outcome name: " + + "market id = %d, variant id = %s, outcome id = %s, language = %s ", + id, + variant, + o.getId(), + language + ) + ) + ); + } + } + + private static Predicate hasMissingName(Locale language) { + return o -> Strings.isNullOrEmpty(o.getName(language)); + } + + private void logIfMarketIsFaulty(DescMarket market, Locale language) { + if (Strings.isNullOrEmpty(market.getName())) { + logger.warn( + format( + "Cached market description without name: " + + "market id = %d, variant id = %s, language = %s ", + market.getId(), + market.getVariant(), + language + ) + ); + } + } + public void merge(DescMarket market, Locale locale) { Preconditions.checkNotNull(market); Preconditions.checkNotNull(locale); - names.put(locale, market.getName()); + Optional + .ofNullable(market.getName()) + .filter(name -> !Strings.isNullOrEmpty(name)) + .ifPresent(name -> names.put(locale, name)); + logIfMarketIsFaulty(market, locale); + outcomeType = combineOutcomeType(market.getOutcomeType(), market.getIncludesOutcomesOfType()); variant = market.getVariant(); if (!Strings.isNullOrEmpty(market.getDescription())) { @@ -170,6 +221,7 @@ public void merge(DescMarket market, Locale locale) { ); } }); + logFaultyOutcomes(locale); } if (market.getMappings() != null) { diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/MarketOutcomeCi.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/MarketOutcomeCi.java index f62e9474..59ccc4a8 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/MarketOutcomeCi.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/MarketOutcomeCi.java @@ -56,7 +56,7 @@ public void merge(DescOutcomes.Outcome o, Locale locale) { Preconditions.checkNotNull(o); Preconditions.checkNotNull(locale); - names.put(locale, o.getName()); + names.put(locale, o.getName() != null ? o.getName() : ""); if (!Strings.isNullOrEmpty(o.getDescription())) { descriptions.put(locale, o.getDescription()); } diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/VariantDescriptionCi.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/VariantDescriptionCi.java index 968b0039..3cf3adcf 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/VariantDescriptionCi.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/ci/markets/VariantDescriptionCi.java @@ -4,12 +4,16 @@ package com.sportradar.unifiedodds.sdk.caching.ci.markets; +import static java.lang.String.format; + import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.sportradar.uf.sportsapi.datamodel.*; import com.sportradar.unifiedodds.sdk.impl.markets.MappingValidatorFactory; import com.sportradar.utils.SdkHelper; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +60,7 @@ public VariantDescriptionCi( .collect(Collectors.toList()) ) .orElse(Collections.emptyList()); + logFaultyOutcomes(dataLocale); mappings = Optional @@ -77,6 +82,27 @@ public VariantDescriptionCi( this.lastDataReceived = new Date(); } + private void logFaultyOutcomes(Locale language) { + outcomes + .stream() + .filter(hasMissingName(language)) + .forEach(o -> + logger.warn( + format( + "Cached variant description without outcome name: " + + "variant id = %s, outcome id = %s, language = %s ", + id, + o.getId(), + language + ) + ) + ); + } + + private static Predicate hasMissingName(Locale language) { + return o -> Strings.isNullOrEmpty(o.getName(language)); + } + public String getId() { return id; } @@ -112,6 +138,7 @@ public void merge(DescVariant market, Locale dataLocale) { } any.ifPresent(marketOutcomeCi -> marketOutcomeCi.merge(map(o), dataLocale)); }); + logFaultyOutcomes(dataLocale); } if (market.getMappings() != null) { diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/exportable/ExportableCompleteRoundCi.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/exportable/ExportableCompleteRoundCi.java index e04f1392..3d8b3c46 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/exportable/ExportableCompleteRoundCi.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/exportable/ExportableCompleteRoundCi.java @@ -39,7 +39,6 @@ public ExportableCompleteRoundCi( List cachedLocales ) { this.names = names; - this.groupNames = groupNames; this.phaseOrGroupLongNames = phaseOrGroupLongNames; this.type = type; this.group = group; diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCache.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCache.java index 9ad934c1..0ac404dd 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCache.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCache.java @@ -9,6 +9,7 @@ import com.sportradar.uf.sportsapi.datamodel.DescMarket; import com.sportradar.uf.sportsapi.datamodel.MarketDescriptions; import com.sportradar.unifiedodds.sdk.caching.ci.markets.MarketDescriptionCi; +import com.sportradar.unifiedodds.sdk.domain.language.Languages; import com.sportradar.unifiedodds.sdk.entities.markets.MarketDescription; import com.sportradar.unifiedodds.sdk.exceptions.internal.CacheItemNotFoundException; import com.sportradar.unifiedodds.sdk.exceptions.internal.DataProviderException; @@ -92,14 +93,14 @@ public InvariantMarketDescriptionCache( } @Override - public MarketDescription getMarketDescriptor(int marketId, String variant, List locales) + public MarketDescription getMarketDescriptor(int marketId, String variant, Languages.BestEffort locales) throws IllegalCacheStateException, CacheItemNotFoundException { Preconditions.checkArgument(marketId > 0); String processingCacheId = String.valueOf(marketId); - MarketDescriptionCi cachedItem = getMarketInternal(processingCacheId, locales); + MarketDescriptionCi cachedItem = getMarketInternal(processingCacheId, locales.getLanguages()); - return new MarketDescriptionImpl(cachedItem, locales); + return new MarketDescriptionImpl(cachedItem, locales.getLanguages()); } @Override diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionCache.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionCache.java index a8437e43..cb657402 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionCache.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionCache.java @@ -4,18 +4,17 @@ package com.sportradar.unifiedodds.sdk.caching.markets; +import com.sportradar.unifiedodds.sdk.domain.language.Languages; import com.sportradar.unifiedodds.sdk.entities.markets.MarketDescription; import com.sportradar.unifiedodds.sdk.exceptions.internal.CacheItemNotFoundException; import com.sportradar.unifiedodds.sdk.exceptions.internal.IllegalCacheStateException; -import java.util.List; -import java.util.Locale; /** * Created on 14/06/2017. * // TODO @eti: Javadoc */ public interface MarketDescriptionCache { - MarketDescription getMarketDescriptor(int marketId, String variant, List locales) + MarketDescription getMarketDescriptor(int marketId, String variant, Languages.BestEffort locales) throws IllegalCacheStateException, CacheItemNotFoundException; boolean loadMarketDescriptions(); diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviderImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviderImpl.java index 73d510d1..156153da 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviderImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviderImpl.java @@ -9,6 +9,7 @@ import com.google.inject.Inject; import com.google.inject.name.Named; import com.sportradar.unifiedodds.sdk.caching.ci.markets.VariantDescriptionCi; +import com.sportradar.unifiedodds.sdk.domain.language.Languages; import com.sportradar.unifiedodds.sdk.entities.markets.MarketDescription; import com.sportradar.unifiedodds.sdk.exceptions.internal.CacheItemNotFoundException; import com.sportradar.unifiedodds.sdk.exceptions.internal.CachingException; @@ -62,7 +63,8 @@ public MarketDescription getMarketDescription( MarketDescription marketDescriptor; try { - marketDescriptor = invariantMarketCache.getMarketDescriptor(marketId, null, locales); + marketDescriptor = + invariantMarketCache.getMarketDescriptor(marketId, null, new Languages.BestEffort(locales)); } catch (CachingException e) { throw new CacheItemNotFoundException( "Market descriptor with id " + marketId + " could not be found", @@ -145,10 +147,12 @@ private MarketDescription provideDynamicVariantEndpointMarket( MarketDescription marketDescriptor, String variantValue ) { - MarketDescription dynamicVariantMarketDescription; try { - dynamicVariantMarketDescription = - variantMarketCache.getMarketDescriptor(marketId, variantValue, locales); + return variantMarketCache.getMarketDescriptor( + marketId, + variantValue, + new Languages.BestEffort(locales) + ); } catch (CachingException e) { logger.warn( "There was an error providing the explicit variant market descriptor -> marketId:{}, variantValue: {}, locales: {}", @@ -159,8 +163,6 @@ private MarketDescription provideDynamicVariantEndpointMarket( ); return marketDescriptor; } - - return dynamicVariantMarketDescription; } private static boolean isMarketPlayerProps(MarketDescription marketDescriptor) { diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCache.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCache.java index b56eca50..82e77e7d 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCache.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCache.java @@ -4,17 +4,21 @@ package com.sportradar.unifiedodds.sdk.caching.markets; +import static java.lang.String.format; + import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.cache.Cache; import com.sportradar.uf.sportsapi.datamodel.DescMarket; import com.sportradar.uf.sportsapi.datamodel.MarketDescriptions; import com.sportradar.unifiedodds.sdk.caching.ci.markets.MarketDescriptionCi; +import com.sportradar.unifiedodds.sdk.domain.language.Languages; import com.sportradar.unifiedodds.sdk.entities.markets.MarketDescription; import com.sportradar.unifiedodds.sdk.exceptions.internal.CacheItemNotFoundException; import com.sportradar.unifiedodds.sdk.exceptions.internal.DataProviderException; import com.sportradar.unifiedodds.sdk.exceptions.internal.IllegalCacheStateException; import com.sportradar.unifiedodds.sdk.impl.DataProvider; +import com.sportradar.unifiedodds.sdk.impl.TimeUtils; import com.sportradar.unifiedodds.sdk.impl.markets.MappingValidatorFactory; import com.sportradar.unifiedodds.sdk.impl.markets.MarketDescriptionImpl; import com.sportradar.utils.SdkHelper; @@ -22,6 +26,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,11 +38,13 @@ public class VariantMarketDescriptionCache implements MarketDescriptionCache { private static final Logger logger = LoggerFactory.getLogger(VariantMarketDescriptionCache.class); + private static final String CACHE_KEY_SEPARATOR = "_"; private final Cache cache; private final DataProvider dataProvider; private final MappingValidatorFactory mappingValidatorFactory; private final ReentrantLock lock = new ReentrantLock(); - private final boolean simpleVariantCaching; + private final TimeUtils time; + private final Config config; private Map fetchedVariants = new ConcurrentHashMap<>(); private Date lastTimeFetchedVariantsWereCleared; @@ -44,24 +52,33 @@ public VariantMarketDescriptionCache( Cache cache, DataProvider dataProvider, MappingValidatorFactory mappingValidatorFactory, - boolean simpleVariantCaching + TimeUtils time, + Config config ) { Preconditions.checkNotNull(cache); Preconditions.checkNotNull(dataProvider); Preconditions.checkNotNull(mappingValidatorFactory); + Preconditions.checkNotNull(time); + Preconditions.checkNotNull(config); this.cache = cache; this.dataProvider = dataProvider; this.mappingValidatorFactory = mappingValidatorFactory; - this.simpleVariantCaching = simpleVariantCaching; - this.lastTimeFetchedVariantsWereCleared = new Date(); + this.time = time; + this.config = config; + this.lastTimeFetchedVariantsWereCleared = new Date(time.now()); } @Override - public MarketDescription getMarketDescriptor(int marketId, String variant, List locales) - throws CacheItemNotFoundException, IllegalCacheStateException { + public MarketDescription getMarketDescriptor( + int marketId, + String variant, + Languages.BestEffort bestEffort + ) throws CacheItemNotFoundException { Preconditions.checkArgument(marketId > 0); Preconditions.checkArgument(!Strings.isNullOrEmpty(variant)); + Preconditions.checkNotNull(bestEffort); + List locales = bestEffort.getLanguages(); Preconditions.checkNotNull(locales); Preconditions.checkArgument(!locales.isEmpty()); @@ -76,26 +93,26 @@ public MarketDescription getMarketDescriptor(int marketId, String variant, List< throw new CacheItemNotFoundException("The requested market descriptor could not be found", e); } - List missingLocales; + List localesToFetch; try { lock.lock(); - missingLocales = getMissingLocales(marketCi, locales); + localesToFetch = missingOrFaultyLanguages(locales, marketCi); } finally { lock.unlock(); } - if (!missingLocales.isEmpty()) { + if (!localesToFetch.isEmpty()) { try { lock.lock(); - if (!isFetchingAllowed(marketId, variant, missingLocales)) { - return new MarketDescriptionImpl(marketCi, locales); - } - missingLocales = getMissingLocales(marketCi, locales); - if (!missingLocales.isEmpty()) { - loadMarketDescriptorData(marketCi, marketId, variant, missingLocales); - - for (Locale l : missingLocales) { - fetchedVariants.put(getFetchedVariantsKey(marketId, variant, l), new Date()); + localesToFetch = missingOrFaultyLanguages(locales, marketCi); + if (!localesToFetch.isEmpty()) { + try { + loadMarketDescriptorData(marketCi, marketId, variant, localesToFetch); + } catch (IllegalCacheStateException e) { + logger.info( + format("variant market[%d %s] failed to be loaded", marketId, variant), + e + ); } } } finally { @@ -106,6 +123,30 @@ public MarketDescription getMarketDescriptor(int marketId, String variant, List< return new MarketDescriptionImpl(marketCi, locales); } + private static List missingOrFaultyLanguages(List locales, MarketDescriptionCi marketCi) { + return locales + .stream() + .filter(l -> missingLanguages(l, marketCi) || faultyLanguage(l, marketCi)) + .collect(Collectors.toList()); + } + + private static boolean faultyLanguage(Locale l, MarketDescriptionCi marketCi) { + boolean languageIsFaulty = marketCi.getName(l) == null || anyOutcomeIsMissingName(l, marketCi); + return marketCi.getCachedLocales().contains(l) && languageIsFaulty; + } + + private static boolean anyOutcomeIsMissingName(Locale l, MarketDescriptionCi marketCi) { + if (marketCi.getOutcomes() != null) { + return marketCi.getOutcomes().stream().map(o -> o.getName(l)).anyMatch(""::equals); + } else { + return false; + } + } + + private static boolean missingLanguages(Locale l, MarketDescriptionCi marketCi) { + return !marketCi.getCachedLocales().contains(l); + } + @Override public boolean loadMarketDescriptions() { return true; @@ -117,9 +158,23 @@ public void deleteCacheItem(int marketId, String variant) { if (cache.asMap().containsKey(cacheId)) { logger.debug("Delete variant market: {}", cacheId); cache.invalidate(cacheId); + invalidateFetchedVariants(marketId, variant); } } + private void invalidateFetchedVariants(int marketId, String variant) { + String marketDescriptionCacheKey = getCacheKey(marketId, variant); + Set fetchedVariantsCacheKeys = fetchedVariants.keySet(); + fetchedVariantsCacheKeys + .stream() + .filter(startingWith(marketDescriptionCacheKey)) + .forEach(fetchedVariants::remove); + } + + private Predicate startingWith(String cacheKeyPrefix) { + return v -> v.startsWith(cacheKeyPrefix + CACHE_KEY_SEPARATOR); + } + @Override public void updateCacheItem(int marketId, String variant) { String cacheId = getCacheKey(marketId, variant); @@ -139,20 +194,20 @@ private MarketDescriptionCi loadMarketDescriptorData( Preconditions.checkArgument(!Strings.isNullOrEmpty(variant)); Preconditions.checkArgument(!locales.isEmpty()); + if (!isFetchingAllowed(marketId, variant, locales)) { + throw new IllegalCacheStateException( + format("Fetching of variant market[%d %s] data is throttled", marketId, variant) + ); + } + try { for (Locale mLoc : locales) { + fetchedVariants.put(getFetchedVariantsKey(marketId, variant, mLoc), new Date(time.now())); MarketDescriptions data = dataProvider.getData(mLoc, String.valueOf(marketId), variant); - if (data == null || data.getMarket().size() != 1) { - throw new IllegalCacheStateException( - "Received variant market[" + - marketId + - " " + - variant + - "] response with invalid market entry count" - ); - } - String cacheId = getCacheKey(marketId, variant); + validateExactlyOneMarketReceived(marketId, variant, data); + validateMarketHasNonZeroId(marketId, variant, data); + DescMarket descMarket = data.getMarket().get(0); if (existingMarketDescriptor == null) { existingMarketDescriptor = @@ -170,29 +225,52 @@ private MarketDescriptionCi loadMarketDescriptorData( return existingMarketDescriptor; } catch (DataProviderException ex) { throw new IllegalCacheStateException( - "An error occurred while fetching variant market[" + marketId + " " + variant + "] data", + format("An error occurred while fetching variant market[%d, %s] data", marketId, variant), ex ); } } - private String getCacheKey(int id, String variant) { - if (simpleVariantCaching) { - return variant; + private static void validateExactlyOneMarketReceived( + int marketId, + String variant, + MarketDescriptions data + ) throws IllegalCacheStateException { + if (data == null || data.getMarket().size() != 1) { + throw new IllegalCacheStateException( + format( + "Received variant market[%d, %s] response with invalid market entry count", + marketId, + variant + ) + ); } - - return id + "_" + variant; } - private List getMissingLocales(MarketDescriptionCi item, List requiredLocales) { - Preconditions.checkNotNull(requiredLocales); - Preconditions.checkArgument(!requiredLocales.isEmpty()); - - if (item == null) { - return requiredLocales; + private static void validateMarketHasNonZeroId( + int marketId, + String variant, + MarketDescriptions descriptions + ) throws IllegalCacheStateException { + List markets = descriptions.getMarket(); + if (markets.size() == 1 && hasIdZero(markets.get(0))) { + throw new IllegalCacheStateException( + format( + "For requested variant market[%d, %s] received a response with invalid market[id=%d]", + marketId, + variant, + markets.get(0).getId() + ) + ); } + } - return SdkHelper.findMissingLocales(item.getCachedLocales(), requiredLocales); + private static boolean hasIdZero(DescMarket descMarket) { + return descMarket != null && descMarket.getId() == 0; + } + + private String getCacheKey(int id, String variant) { + return id + CACHE_KEY_SEPARATOR + variant; } private boolean isFetchingAllowed(int marketId, String variant, List locales) { @@ -205,16 +283,16 @@ private boolean isFetchingAllowed(int marketId, String variant, List loc } private boolean isFetchingAllowed(int marketId, String variant, Locale locale) { - if (fetchedVariants.size() > 1000) { + if (fetchedVariants.size() > config.getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom()) { clearFetchedVariants(); } String cacheKey = getFetchedVariantsKey(marketId, variant, locale); - if (!fetchedVariants.containsKey(cacheKey)) { + Date date = fetchedVariants.get(cacheKey); + if (date == null) { return true; } - Date date = fetchedVariants.get(cacheKey); - if (SdkHelper.getTimeDifferenceInSeconds(new Date(), date) > 30) { + if (SdkHelper.getTimeDifferenceInSeconds(new Date(time.now()), date) > 30) { return true; } clearFetchedVariants(); @@ -223,23 +301,36 @@ private boolean isFetchingAllowed(int marketId, String variant, Locale locale) { } private String getFetchedVariantsKey(int marketId, String variant, Locale locale) { - return getCacheKey(marketId, variant) + "_" + locale; + return getCacheKey(marketId, variant) + CACHE_KEY_SEPARATOR + locale; } /** * clear records from fetchedVariants once a min */ private void clearFetchedVariants() { - if (SdkHelper.getTimeDifferenceInSeconds(new Date(), lastTimeFetchedVariantsWereCleared) > 60) { + if ( + SdkHelper.getTimeDifferenceInSeconds(new Date(time.now()), lastTimeFetchedVariantsWereCleared) > + 60 + ) { Set keys = fetchedVariants.keySet(); for (String key : keys) { Date currDate = fetchedVariants.get(key); - if (SdkHelper.getTimeDifferenceInSeconds(new Date(), currDate) > 30) { + if ( + currDate != null && + SdkHelper.getTimeDifferenceInSeconds(new Date(time.now()), currDate) > 30 + ) { fetchedVariants.remove(key); } } - lastTimeFetchedVariantsWereCleared = new Date(); + lastTimeFetchedVariantsWereCleared = new Date(time.now()); + } + } + + public static class Config { + + public int getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom() { + return 1000; } } } diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/di/CachingModule.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/di/CachingModule.java index 53338e96..61d7ed03 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/di/CachingModule.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/di/CachingModule.java @@ -223,7 +223,8 @@ protected MarketDescriptionCache provideVariantMarketDescriptionCache( SdkInternalConfiguration cfg, LogFastHttpDataFetcher httpDataFetcher, @Named("SportsApiJaxbDeserializer") Deserializer deserializer, - MappingValidatorFactory mappingFactory + MappingValidatorFactory mappingFactory, + TimeUtils timeUtils ) { return new VariantMarketDescriptionCache( internalCachesProvider.getVariantMarketCache(), @@ -234,7 +235,8 @@ protected MarketDescriptionCache provideVariantMarketDescriptionCache( deserializer ), mappingFactory, - cfg.getSimpleVariantCaching() + timeUtils, + new VariantMarketDescriptionCache.Config() ); } diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/di/HttpClientModule.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/di/HttpClientModule.java index 19d870d1..91fc5cfc 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/di/HttpClientModule.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/di/HttpClientModule.java @@ -96,11 +96,6 @@ private HttpHelper provideRecoveryHttpHelper( @Named("SportsApiJaxbDeserializer") Deserializer apiDeserializer, UserAgentProvider userAgentProvider ) { - return new HttpHelper( - config, - httpClient, - new MessageAndActionExtractor(apiDeserializer), - userAgentProvider - ); + return new HttpHelper(config, httpClient, new MessageAndActionExtractor(), userAgentProvider); } } diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/domain/language/Languages.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/domain/language/Languages.java new file mode 100644 index 00000000..f9b8d86a --- /dev/null +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/domain/language/Languages.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.domain.language; + +import com.google.common.collect.ImmutableList; +import java.util.*; + +public final class Languages { + + public static final class BestEffort { + + private final List languages; + + public BestEffort(Locale language, Locale... otherLanguages) { + this.languages = ImmutableList.builder().add(language).add(otherLanguages).build(); + } + + public BestEffort(List languages) { + this.languages = ImmutableList.copyOf(languages); + } + + public List getLanguages() { + return languages; + } + } +} diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/domain/language/Translations.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/domain/language/Translations.java index 270b0268..342e54df 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/domain/language/Translations.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/domain/language/Translations.java @@ -5,10 +5,7 @@ import com.google.common.base.Preconditions; import java.io.Serializable; -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; +import java.util.*; public class Translations implements Serializable { diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumer.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumer.java index e14ff875..c5b0e6e7 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumer.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumer.java @@ -5,6 +5,7 @@ package com.sportradar.unifiedodds.sdk.impl; import com.rabbitmq.client.AMQP; +import java.io.IOException; /** * Defines methods implemented by classes that can handle message payloads @@ -28,4 +29,10 @@ public interface ChannelMessageConsumer { void onMessageReceived(String routingKey, byte[] body, AMQP.BasicProperties properties, long receivedAt); String getConsumerDescription(); + + /** + * Closes the channel and does the resource cleanup + * + */ + void close() throws IOException; } diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumerImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumerImpl.java index 69d2aba1..9876b14c 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumerImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumerImpl.java @@ -17,6 +17,7 @@ import com.sportradar.unifiedodds.sdk.oddsentities.UnmarshalledMessage; import com.sportradar.utils.Urn; import java.io.ByteArrayInputStream; +import java.io.IOException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; @@ -313,4 +314,9 @@ private Unmarshaller getMessageJaxbUnmarshaller() { return messageJaxbUnmarshaller.get(); } + + @Override + public void close() throws IOException { + messageJaxbUnmarshaller.remove(); + } } diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/DataWrapper.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/DataWrapper.java index bc85cacb..fdc8c0fe 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/DataWrapper.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/DataWrapper.java @@ -33,7 +33,7 @@ public class DataWrapper { private final Map headers; private final ZonedDateTime serverResponseTime; - DataWrapper(T data, Header[] headers) { + public DataWrapper(T data, Header[] headers) { this.data = data; if (headers != null) { diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/HttpResponseHandler.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/HttpResponseHandler.java index f405b187..7c002f41 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/HttpResponseHandler.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/HttpResponseHandler.java @@ -21,12 +21,6 @@ public class HttpResponseHandler { private static final Logger LOGGER = LoggerFactory.getLogger(HttpResponseHandler.class); - private final Deserializer apiDeserializer; - - @Inject - public HttpResponseHandler(@Named("SportsApiJaxbDeserializer") Deserializer apiDeserializer) { - this.apiDeserializer = apiDeserializer; - } HttpData extractHttpDataFromHttpResponse(ClassicHttpResponse httpResponse, String path) { try { @@ -77,8 +71,7 @@ private void validateStatusCode(ClassicHttpResponse httpResponse, String path) t private String logAndReturnErrorMessage(ClassicHttpResponse httpResponse, String path) throws IOException { - String errorMessage = new MessageAndActionExtractor(apiDeserializer) - .parse(httpResponse.getEntity().getContent()); + String errorMessage = new MessageAndActionExtractor().parse(httpResponse.getEntity().getContent()); LOGGER.warn( "Bad API response: " + httpResponse.getVersion() + diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/apireaders/MessageAndActionExtractor.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/apireaders/MessageAndActionExtractor.java index 8b128a3c..6f3c14af 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/apireaders/MessageAndActionExtractor.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/apireaders/MessageAndActionExtractor.java @@ -4,48 +4,47 @@ package com.sportradar.unifiedodds.sdk.impl.apireaders; import com.google.common.base.Preconditions; -import com.google.inject.Inject; -import com.google.inject.name.Named; -import com.sportradar.uf.sportsapi.datamodel.APIPageNotFound; -import com.sportradar.uf.sportsapi.datamodel.Response; -import com.sportradar.unifiedodds.sdk.exceptions.internal.DeserializationException; -import com.sportradar.unifiedodds.sdk.impl.Deserializer; import java.io.InputStream; +import java.util.Optional; +import javax.xml.bind.DataBindingException; +import javax.xml.bind.JAXB; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; public class MessageAndActionExtractor { - private Deserializer apiDeserializer; - - @Inject - public MessageAndActionExtractor(@Named("SportsApiJaxbDeserializer") Deserializer apiDeserializer) { - Preconditions.checkNotNull(apiDeserializer); - - this.apiDeserializer = apiDeserializer; - } + private static final Logger LOGGER = LoggerFactory.getLogger(MessageAndActionExtractor.class); public String parse(InputStream httpResponseContent) { - Preconditions.checkNotNull(apiDeserializer); Preconditions.checkNotNull(httpResponseContent); - - String errMsg; try { - Object deserializedResponse = apiDeserializer.deserialize(httpResponseContent); - if (deserializedResponse instanceof APIPageNotFound) { - errMsg = ((APIPageNotFound) deserializedResponse).getMessage(); - } else if (deserializedResponse instanceof Response) { - Response response = (Response) deserializedResponse; - errMsg = - Concatenator - .separatingWith(", ") - .appendIfNotNull(response.getMessage()) - .appendIfNotNull(response.getAction()) - .retrieve(); - } else { - errMsg = "Unknown response format, " + deserializedResponse.getClass().getName(); - } - } catch (DeserializationException e) { - errMsg = "No specific message"; + Element deserializedResponse = (Element) JAXB.unmarshal(httpResponseContent, Object.class); + return extractMessageFromXml(deserializedResponse); + } catch (DataBindingException | ClassCastException e) { + LOGGER.warn("Failed to parse the response as XML", e); + return "No specific message"; } - return errMsg; + } + + private static String extractMessageFromXml(Element element) { + Optional message = getTextContentFromFirstChildElement(element, "message"); + Optional action = getTextContentFromFirstChildElement(element, "action"); + return Concatenator + .separatingWith(", ") + .appendIfNotNull(message.orElse(null)) + .appendIfNotNull(action.orElse(null)) + .retrieve(); + } + + private static Optional getTextContentFromFirstChildElement(Element element, String tagName) { + NodeList children = element.getElementsByTagName(tagName); + return Optional + .of(children) + .filter(ch -> ch.getLength() > 0) + .map(ch -> ch.item(0)) + .map(Node::getTextContent); } } diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/apireaders/WhoAmIReader.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/apireaders/WhoAmIReader.java index 9c892786..558f06e8 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/apireaders/WhoAmIReader.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/apireaders/WhoAmIReader.java @@ -4,6 +4,8 @@ package com.sportradar.unifiedodds.sdk.impl.apireaders; +import static java.util.Arrays.asList; + import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; @@ -176,7 +178,8 @@ private void retrieveInfo() { return; } - BookmakerDetails bookmakerDetails = config.getEnvironment() == Environment.Replay + BookmakerDetails bookmakerDetails = asList(Environment.GlobalReplay, Environment.Replay) + .contains(config.getEnvironment()) ? fetchReplayBookmakerDetails() : fetchBookmakerDetails(); diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentImpl.java index 161207ab..e55330e6 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentImpl.java @@ -151,7 +151,7 @@ public Boolean isStartTimeTbd() { return null; } - return tournamentCi.isStartTimeTbd().isPresent() ? tournamentCi.isStartTimeTbd().get() : null; + return tournamentCi.isStartTimeTbd().orElse(null); } /** diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawImpl.java index 4ba26918..891778be 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawImpl.java @@ -6,6 +6,7 @@ import com.google.common.base.Preconditions; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.SportEntityFactory; import com.sportradar.unifiedodds.sdk.caching.DrawCi; import com.sportradar.unifiedodds.sdk.caching.SportEventCache; import com.sportradar.unifiedodds.sdk.caching.SportEventCi; @@ -32,7 +33,7 @@ public class DrawImpl extends SportEventImpl implements Draw { private final List locales; private final SportEventCache sportEventCache; - private final SportEntityFactoryImpl sportEntityFactory; + private final SportEntityFactory sportEntityFactory; private final ExceptionHandlingStrategy exceptionHandlingStrategy; public DrawImpl( @@ -40,7 +41,7 @@ public DrawImpl( Urn sportId, List locales, SportEventCache sportEventCache, - SportEntityFactoryImpl sportEntityFactory, + SportEntityFactory sportEntityFactory, ExceptionHandlingStrategy exceptionHandlingStrategy ) { super(id, sportId); @@ -202,7 +203,7 @@ public Boolean isStartTimeTbd() { return null; } - return drawCi.isStartTimeTbd().isPresent() ? drawCi.isStartTimeTbd().get() : null; + return drawCi.isStartTimeTbd().orElse(null); } /** diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryImpl.java index a95ed830..b0c3254b 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryImpl.java @@ -6,6 +6,7 @@ import com.google.common.base.Preconditions; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.SportEntityFactory; import com.sportradar.unifiedodds.sdk.caching.LotteryCi; import com.sportradar.unifiedodds.sdk.caching.SportEventCache; import com.sportradar.unifiedodds.sdk.caching.SportEventCi; @@ -13,7 +14,6 @@ import com.sportradar.unifiedodds.sdk.exceptions.ObjectNotFoundException; import com.sportradar.unifiedodds.sdk.exceptions.internal.CacheItemNotFoundException; import com.sportradar.unifiedodds.sdk.exceptions.internal.StreamWrapperException; -import com.sportradar.unifiedodds.sdk.impl.SportEntityFactoryImpl; import com.sportradar.utils.Urn; import java.util.Date; import java.util.List; @@ -42,7 +42,7 @@ public class LotteryImpl extends SportEventImpl implements Lottery { private final List locales; private final SportEventCache sportEventCache; - private final SportEntityFactoryImpl sportEntityFactory; + private final SportEntityFactory sportEntityFactory; private final ExceptionHandlingStrategy exceptionHandlingStrategy; public LotteryImpl( @@ -50,7 +50,7 @@ public LotteryImpl( Urn sportId, List locales, SportEventCache sportEventCache, - SportEntityFactoryImpl sportEntityFactory, + SportEntityFactory sportEntityFactory, ExceptionHandlingStrategy exceptionHandlingStrategy ) { super(id, sportId); @@ -314,7 +314,7 @@ public Boolean isStartTimeTbd() { return null; } - return lotteryCi.isStartTimeTbd().isPresent() ? lotteryCi.isStartTimeTbd().get() : null; + return lotteryCi.isStartTimeTbd().orElse(null); } /** diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/MatchImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/MatchImpl.java index a3ee538d..6556efb0 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/MatchImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/MatchImpl.java @@ -474,7 +474,7 @@ public Boolean isStartTimeTbd() { return null; } - return cacheItem.isStartTimeTbd().isPresent() ? cacheItem.isStartTimeTbd().get() : null; + return cacheItem.isStartTimeTbd().orElse(null); } /** diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/StageImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/StageImpl.java index 335a6550..7098f4cd 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/StageImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/StageImpl.java @@ -457,7 +457,7 @@ public Boolean isStartTimeTbd() { return null; } - return cacheItem.isStartTimeTbd().isPresent() ? cacheItem.isStartTimeTbd().get() : null; + return cacheItem.isStartTimeTbd().orElse(null); } /** diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentImpl.java index 85a5e129..270596f4 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentImpl.java @@ -21,6 +21,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -164,7 +165,8 @@ public Boolean isStartTimeTbd() { return null; } - return tournamentCi.isStartTimeTbd().isPresent() ? tournamentCi.isStartTimeTbd().get() : null; + Optional startTimeTbd = tournamentCi.isStartTimeTbd(); + return startTimeTbd.orElse(null); } /** diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/MarketDescriptionImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/MarketDescriptionImpl.java index e264514d..12d35925 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/MarketDescriptionImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/MarketDescriptionImpl.java @@ -49,7 +49,11 @@ public MarketDescriptionImpl(MarketDescriptionCi cachedItem, List locale id = cachedItem.getId(); outcomeType = cachedItem.getOutcomeType(); groups = cachedItem.getGroups(); - names = locales.stream().collect(ImmutableMap.toImmutableMap(k -> k, cachedItem::getName)); + names = + locales + .stream() + .filter(locale -> cachedItem.getName(locale) != null) + .collect(ImmutableMap.toImmutableMap(k -> k, cachedItem::getName)); descriptions = locales diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/MarketManagerImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/MarketManagerImpl.java index dc1a760e..3e91b5d7 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/MarketManagerImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/MarketManagerImpl.java @@ -338,7 +338,6 @@ public long parallelPrefetchVariantMarketDescriptions( ); threadPool.invokeAll(tasks); } catch (InterruptedException e) { - e.printStackTrace(); Thread.currentThread().interrupt(); } catch (Exception ex) { interactionLogger.error("Error prefetching variant market descriptions.", ex); diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderImpl.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderImpl.java index 631294f1..98db5ee6 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderImpl.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderImpl.java @@ -416,8 +416,8 @@ private String handleErrorCondition( Locale locale, Exception ex ) { - return handleErrorCondition(message, outcomeId, nameDescriptor, Collections.singletonList(locale), ex) - .get(locale); + handleErrorCondition(message, outcomeId, nameDescriptor, Collections.singletonList(locale), ex); + return null; } private Map handleErrorCondition( @@ -529,10 +529,7 @@ private MarketDescription getMarketDescriptionForOutcome( .stream() .filter(o -> o.getId().equals(outcomeId)) .findFirst(); - if ( - !optDesc.isPresent() || - !SdkHelper.findMissingLocales(optDesc.get().getLocales(), locales).isEmpty() - ) { + if (!optDesc.isPresent()) { if (firstTime) { logger.warn( new StatusMessage( diff --git a/sdk-core/src/main/java/com/sportradar/utils/thread/sleep/Sleep.java b/sdk-core/src/main/java/com/sportradar/utils/thread/sleep/Sleep.java index c265a63e..ea8d64e8 100644 --- a/sdk-core/src/main/java/com/sportradar/utils/thread/sleep/Sleep.java +++ b/sdk-core/src/main/java/com/sportradar/utils/thread/sleep/Sleep.java @@ -15,6 +15,7 @@ public void millis(int amount) { Thread.sleep(amount); } catch (InterruptedException e) { LOGGER.warn("Interrupted", e); + Thread.currentThread().interrupt(); } } } diff --git a/sdk-core/src/main/resources/javadoc/unified-odds-overview.html b/sdk-core/src/main/resources/javadoc/unified-odds-overview.html index f9aae2d0..14728c0c 100644 --- a/sdk-core/src/main/resources/javadoc/unified-odds-overview.html +++ b/sdk-core/src/main/resources/javadoc/unified-odds-overview.html @@ -171,6 +171,5 @@

References

  • UofListener
  • Message
  • SportEvent
  • -
  • SportEventStatus
  • diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/BookingManagerTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/BookingManagerTest.java index a3437acd..3b763597 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/BookingManagerTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/BookingManagerTest.java @@ -50,7 +50,7 @@ public void setup() { Key.get(Deserializer.class, Names.named("SportsApiJaxbDeserializer")) ); when(configInternal.getExceptionHandlingStrategy()).thenReturn(ExceptionHandlingStrategy.Throw); - val messageExtractor = new MessageAndActionExtractor(deserializer); + val messageExtractor = new MessageAndActionExtractor(); testHttpHelper = new TestHttpHelper(configInternal, httpClient, messageExtractor, userAgent); bookingManager = new BookingManagerImpl(sportEventCache, configInternal, testHttpHelper); } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ConfigureMtsSpecificTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ConfigureMtsSpecificTest.java index 71fe6703..78d768eb 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ConfigureMtsSpecificTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ConfigureMtsSpecificTest.java @@ -47,20 +47,6 @@ public void configuringCleanTrafficLogEntriesIsNoLongerNecessaryAsMtsIsNotUsingI assertEquals(false, internalConfigForReplay.isCleanTrafficLogEntriesEnabled()); } - @Test - public void configuringSimpleVariantCachingIsNoLongerNecessaryAsMtsIsNotUsingIt() { - UofConfiguration config = buildFromPropsFile - .setAccessToken("any") - .selectCustom() - .setDefaultLanguage(anyLanguage) - .build(); - val internalConfig = new SdkInternalConfiguration(config, anyProps(), anyYaml()); - val internalConfigForReplay = new SdkInternalConfiguration(config, replayMode, anyProps(), anyYaml()); - - assertEquals(false, internalConfig.getSimpleVariantCaching()); - assertEquals(false, internalConfigForReplay.getSimpleVariantCaching()); - } - @Test public void configuringSchedulerTasksToSkipIsNoLongerNecessaryAsMtsIsNotUsingIt() { UofConfiguration config = buildFromPropsFile diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/CustomBetIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/CustomBetIT.java new file mode 100644 index 00000000..ff9fc4d7 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/CustomBetIT.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk; + +import static com.sportradar.unifiedodds.sdk.impl.Constants.RABBIT_BASE_URL; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import com.sportradar.unifiedodds.sdk.conn.ApiSimulator; +import com.sportradar.unifiedodds.sdk.conn.SdkSetup; +import com.sportradar.unifiedodds.sdk.custombetentities.Selection; +import com.sportradar.unifiedodds.sdk.impl.Constants; +import com.sportradar.unifiedodds.sdk.impl.custombetentities.SelectionImpl; +import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.BaseUrl; +import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.Credentials; +import com.sportradar.utils.Urn; +import com.sportradar.utils.Urns; +import java.util.List; +import java.util.concurrent.Callable; +import lombok.SneakyThrows; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.ExceptionUtils; + +@WireMockTest +public class CustomBetIT { + + private final Credentials sdkCredentials = Credentials.with( + Constants.SDK_USERNAME, + Constants.SDK_PASSWORD + ); + private BaseUrl sportsApiBaseUrl; + private ApiSimulator apiSimulator; + + @BeforeEach + void setup(WireMockRuntimeInfo wmRuntimeInfo) { + sportsApiBaseUrl = BaseUrl.of("localhost", wmRuntimeInfo.getHttpPort()); + apiSimulator = new ApiSimulator(wmRuntimeInfo.getWireMock()); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + } + + @Nested + class ErrorResponses { + + private final Selection selection = new SelectionImpl( + Urns.SportEvents.getForAnyMatch(), + 556, + "pre:outcometext:5547248", + "pre:markettext:225275" + ); + private UofSdk sdk; + + @BeforeEach + void createSdk() throws Exception { + sdk = + SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, 1) + .with(ExceptionHandlingStrategy.Throw) + .withoutFeed(); + } + + @AfterEach + void disposeSdk() throws Exception { + sdk.close(); + } + + @Test + void errorMessagesFromDifferentCallsAreExposedToTheCustomer() { + apiSimulator.returnNotFoundForCustomBetAvailableSelections(); + apiSimulator.returnNotFoundForCustomBetCalculate(); + + Urn eventId = Urns.SportEvents.getForAnyMatch(); + + assertThat( + rootCauseExceptionMessage(() -> sdk.getCustomBetManager().getAvailableSelections(eventId)) + ) + .isNotEqualTo( + rootCauseExceptionMessage(() -> + sdk.getCustomBetManager().calculateProbability(singletonList(selection)) + ) + ); + } + + @SneakyThrows + @SuppressWarnings("IllegalCatch") + private String rootCauseExceptionMessage(Callable routine) { + try { + routine.call(); + throw new IllegalStateException("Exception expected"); + } catch (Throwable e) { + List exceptions = ExceptionUtils.findNestedThrowables(e); + return exceptions.get(exceptions.size() - 1).getMessage(); + } + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/DiExerciserForUnitTestsToGainCoverageAsDiIsTestedAtIntegrationLevelTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/DiExerciserForUnitTestsToGainCoverageAsDiIsTestedAtIntegrationLevelTest.java index 9487c2a7..8e4082b2 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/DiExerciserForUnitTestsToGainCoverageAsDiIsTestedAtIntegrationLevelTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/DiExerciserForUnitTestsToGainCoverageAsDiIsTestedAtIntegrationLevelTest.java @@ -7,11 +7,13 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.sportradar.unifiedodds.sdk.cfg.CustomConfigurationBuilder; import java.util.Locale; +import lombok.val; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -30,7 +32,7 @@ public void stubBookmakerAndProducers() { @Test public void forArgumentlessConfigurationBuilderFactoryMethodAsItEffectivelyDoesDi() { Locale anyLanguage = Locale.ENGLISH; - againstWiremock( + val config = againstWiremock( UofSdk .getUofConfigurationBuilder() .setAccessToken("any") @@ -38,12 +40,15 @@ public void forArgumentlessConfigurationBuilderFactoryMethodAsItEffectivelyDoesD .setDefaultLanguage(anyLanguage) ) .build(); + + assertThat(config).isNotNull(); } @Test public void forConfigurationBuilderFactoryMethodWithUserSpecifiedFilesAsItEffectivelyDoesDi() { Locale anyLanguage = Locale.ENGLISH; - againstWiremock( + + val config = againstWiremock( UofSdk .getUofConfigurationBuilder("UFSdkConfiguration.properties", "any") .setAccessToken("any") @@ -51,6 +56,8 @@ public void forConfigurationBuilderFactoryMethodWithUserSpecifiedFilesAsItEffect .setDefaultLanguage(anyLanguage) ) .build(); + + assertThat(config).isNotNull(); } private CustomConfigurationBuilder againstWiremock(CustomConfigurationBuilder builder) { diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ThreadInterruptTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ThreadInterruptTest.java index 8d3951fe..4db18b69 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ThreadInterruptTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ThreadInterruptTest.java @@ -1,29 +1,49 @@ package com.sportradar.unifiedodds.sdk; +import static org.assertj.core.api.Assertions.assertThatNoException; + import org.junit.Test; -@SuppressWarnings({ "MagicNumber", "MultipleStringLiterals" }) +@SuppressWarnings({ "MagicNumber", "MultipleStringLiterals", "LambdaBodyLength" }) public class ThreadInterruptTest { @Test public void test_interrupt() { - Thread t = new Thread(this::run); - System.out.println( - "Name : " + t.getName() + ", state : " + t.getState() + ", interrupted? " + t.isInterrupted() - ); - t.start(); - try { - t.join(); - } catch (InterruptedException e) { - System.out.println( - "Name : " + t.getName() + ", state : " + t.getState() + ", interrupted? " + t.isInterrupted() - ); - throw new RuntimeException(e); - } + assertThatNoException() + .isThrownBy(() -> { + Thread t = new Thread(this::run); + System.out.println( + "Name : " + + t.getName() + + ", state : " + + t.getState() + + ", interrupted? " + + t.isInterrupted() + ); + t.start(); + try { + t.join(); + } catch (InterruptedException e) { + System.out.println( + "Name : " + + t.getName() + + ", state : " + + t.getState() + + ", interrupted? " + + t.isInterrupted() + ); + throw new RuntimeException(e); + } - System.out.println( - "Name : " + t.getName() + ", state : " + t.getState() + ", interrupted? " + t.isInterrupted() - ); + System.out.println( + "Name : " + + t.getName() + + ", state : " + + t.getState() + + ", interrupted? " + + t.isInterrupted() + ); + }); } public void run() { diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/DataProviderAnswers.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/DataProviderAnswers.java new file mode 100644 index 00000000..000b98e6 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/DataProviderAnswers.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.markets; + +import com.sportradar.unifiedodds.sdk.exceptions.internal.DataProviderException; +import org.mockito.stubbing.Answer; + +public class DataProviderAnswers { + + public static Answer withGetDataThrowingByDefault() { + return inv -> { + if (inv.getMethod().getName().startsWith("getData")) { + throw new DataProviderException("Failed to fetch from a DataProvider Stub"); + } + throw new IllegalStateException( + "DataProviderStub is no prepared to handle this method. Consider using Mockito.mock()" + ); + }; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCacheTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCacheTest.java new file mode 100644 index 00000000..e4cfe024 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCacheTest.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.markets; + +import static com.sportradar.unifiedodds.sdk.caching.markets.InvariantMarketDescriptionCaches.stubbingOutDataProvidersAndScheduler; +import static com.sportradar.unifiedodds.sdk.caching.markets.Sources.*; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds.ODD_EVEN_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.impl.MarketDescriptionDataProviders.providingList; +import static com.sportradar.utils.domain.names.LanguageHolder.in; +import static java.util.Locale.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.*; + +import com.sportradar.uf.sportsapi.datamodel.DescMarket; +import com.sportradar.unifiedodds.sdk.domain.language.Languages; +import com.sportradar.unifiedodds.sdk.entities.markets.MarketDescription; +import com.sportradar.unifiedodds.sdk.exceptions.internal.CacheItemNotFoundException; +import com.sportradar.unifiedodds.sdk.testutil.generic.concurrent.VoidCallables; +import java.util.Locale; +import java.util.stream.Stream; +import lombok.val; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class InvariantMarketDescriptionCacheTest { + + private static final String IRRELEVANT = null; + private static final String NULLIFY_OR_EMPTY_MARKET_NAME = + "com.sportradar.unifiedodds.sdk.caching.markets.Sources#nullifyOrEmptyMarketName"; + + @Nested + class GetMarketDescriptor { + + @ParameterizedTest + @ValueSource(strings = { "en", "fr" }) + public void translatesName(String language) throws Exception { + val aLanguage = new Locale(language); + val cache = stubbingOutDataProvidersAndScheduler() + .with(providingList(in(aLanguage), oddEvenMarketDescription(aLanguage))) + .build(); + + val description = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(aLanguage) + ); + + assertThat(description.getName(aLanguage)) + .isEqualTo(oddEvenMarketDescription(aLanguage).getName()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME) + public void notTranslatesMissingNames(NameRemover marketName) throws Exception { + val aLanguage = ENGLISH; + + DescMarket marketDescription = marketName.remove(oddEvenMarketDescription(aLanguage)); + + val cache = stubbingOutDataProvidersAndScheduler() + .with(providingList(in(aLanguage), marketDescription)) + .build(); + + val description = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(aLanguage) + ); + + assertThat(description.getName(aLanguage)).isNull(); + } + + @Test + public void cachesName() throws Exception { + val aLanguage = ENGLISH; + val dataProvider = providingList(in(aLanguage), oddEvenMarketDescription(aLanguage)); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + val description1 = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(aLanguage) + ); + + val description2 = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(aLanguage) + ); + + assertThat(description1.getName(aLanguage)).isEqualTo(description2.getName(aLanguage)); + verify(dataProvider).getData(aLanguage); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME) + public void cachesMissingName(NameRemover marketName) throws Exception { + val aLanguage = ENGLISH; + val dataProvider = providingList( + in(aLanguage), + marketName.remove(oddEvenMarketDescription(aLanguage)) + ); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + val description1 = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(aLanguage) + ); + + val description2 = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(aLanguage) + ); + + assertThat(description1.getName(aLanguage)).isNull(); + assertThat(description2.getName(aLanguage)).isNull(); + verify(dataProvider).getData(aLanguage); + } + + @Test + public void unknownMarketsNotReFetchesToProtectDataSourceAfterInitialFetchingOfMarkets() + throws Exception { + val aLanguage = ENGLISH; + final int unknownMarketId = 123456; + val dataProvider = providingList(in(aLanguage), oddEvenMarketDescription()); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + cache.getMarketDescriptor(ODD_EVEN_MARKET_ID, IRRELEVANT, new Languages.BestEffort(aLanguage)); + expectNotToFind(() -> + cache.getMarketDescriptor(unknownMarketId, IRRELEVANT, new Languages.BestEffort(aLanguage)) + ); + expectNotToFind(() -> + cache.getMarketDescriptor(unknownMarketId, IRRELEVANT, new Languages.BestEffort(aLanguage)) + ); + + verify(dataProvider, times(1)).getData(aLanguage); + } + + @Test + public void unknownMarketsNotReFetchesToProtectDataSource() throws Exception { + val aLanguage = ENGLISH; + final int unknownMarketId = 123456; + val dataProvider = providingList(in(aLanguage), oddEvenMarketDescription()); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + expectNotToFind(() -> + cache.getMarketDescriptor(unknownMarketId, IRRELEVANT, new Languages.BestEffort(aLanguage)) + ); + expectNotToFind(() -> + cache.getMarketDescriptor(unknownMarketId, IRRELEVANT, new Languages.BestEffort(aLanguage)) + ); + + verify(dataProvider, times(1)).getData(aLanguage); + } + + @Test + public void validMarketsAreCachedDuringInitialFetchCausedByQueryingUnknownMarket() throws Exception { + val aLanguage = ENGLISH; + final int unknownMarketId = 123456; + val dataProvider = providingList(in(aLanguage), oddEvenMarketDescription()); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + expectNotToFind(() -> + cache.getMarketDescriptor(unknownMarketId, IRRELEVANT, new Languages.BestEffort(aLanguage)) + ); + val description = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(aLanguage) + ); + + assertThat(description.getName(aLanguage)).isEqualTo(oddEvenMarketDescription().getName()); + verify(dataProvider, times(1)).getData(aLanguage); + } + + @Test + public void unknownMarketsNotCauseReFetchingToProtectDataSourceForLanguagesNotSeenYet() + throws Exception { + val langA = ENGLISH; + val langB = FRENCH; + final int unknownMarketId = 123456; + val dataProvider = providingList( + in(langA), + oddEvenMarketDescription(langA), + in(langB), + oddEvenMarketDescription(langB) + ); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + cache.getMarketDescriptor(ODD_EVEN_MARKET_ID, IRRELEVANT, new Languages.BestEffort(langA)); + expectNotToFind(() -> + cache.getMarketDescriptor(unknownMarketId, IRRELEVANT, new Languages.BestEffort(langB)) + ); + expectNotToFind(() -> + cache.getMarketDescriptor(unknownMarketId, IRRELEVANT, new Languages.BestEffort(langB)) + ); + + verify(dataProvider, times(1)).getData(langA); + verify(dataProvider, times(1)).getData(langB); + } + + @Test + public void translatesNameToMultipleLanguagesAtOnce() throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + val dataProvider = providingList( + in(languageA), + oddEvenMarketDescription(languageA), + in(languageB), + oddEvenMarketDescription(languageB) + ); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + val description = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(languageA, languageB) + ); + + assertThat(description.getName(languageA)) + .isEqualTo(oddEvenMarketDescription(languageA).getName()); + assertThat(description.getName(languageB)) + .isEqualTo(oddEvenMarketDescription(languageB).getName()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME) + public void cachesNameForLanguagesItIsAvailableInStartingWithAvailableOne(NameRemover marketName) + throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + + DescMarket faultyDescription = marketName.remove(oddEvenMarketDescription(languageB)); + val dataProvider = providingList( + in(languageA), + oddEvenMarketDescription(languageA), + in(languageB), + faultyDescription + ); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + val nameInLangA = cache + .getMarketDescriptor(ODD_EVEN_MARKET_ID, IRRELEVANT, new Languages.BestEffort(languageA)) + .getName(languageA); + val noNameInLangeB = cache + .getMarketDescriptor(ODD_EVEN_MARKET_ID, IRRELEVANT, new Languages.BestEffort(languageB)) + .getName(languageB); + + assertThat(nameInLangA).isEqualTo(oddEvenMarketDescription(languageA).getName()); + assertThat(noNameInLangeB).isNull(); + } + + @Test + public void cachesNameForLanguagesItIsAvailableInStartingWithMissingOne() throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + + DescMarket faultyDescription = nullifyMarketName(oddEvenMarketDescription(languageB)); + val dataProvider = providingList( + in(languageA), + oddEvenMarketDescription(languageA), + in(languageB), + faultyDescription + ); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + val noNameInLangeB = cache + .getMarketDescriptor(ODD_EVEN_MARKET_ID, IRRELEVANT, new Languages.BestEffort(languageB)) + .getName(languageB); + val nameInLangA = cache + .getMarketDescriptor(ODD_EVEN_MARKET_ID, IRRELEVANT, new Languages.BestEffort(languageA)) + .getName(languageA); + + assertThat(noNameInLangeB).isNull(); + assertThat(nameInLangA).isEqualTo(oddEvenMarketDescription(languageA).getName()); + } + + @Test + public void cachesNameForLanguagesItIsAvailableInBatchStartingWithAvailableOne() throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + + DescMarket faultyDescription = nullifyMarketName(oddEvenMarketDescription(languageB)); + val dataProvider = providingList( + in(languageA), + oddEvenMarketDescription(languageA), + in(languageB), + faultyDescription + ); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + MarketDescription desc = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(languageA, languageB) + ); + + assertThat(desc.getName(languageA)).isEqualTo(oddEvenMarketDescription(languageA).getName()); + assertThat(desc.getName(languageB)).isNull(); + } + + @Test + public void cachesNameForLanguagesItIsAvailableInBatchStartingWithMissingOne() throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + + DescMarket faultyDescription = nullifyMarketName(oddEvenMarketDescription(languageB)); + val dataProvider = providingList( + in(languageA), + oddEvenMarketDescription(languageA), + in(languageB), + faultyDescription + ); + + val cache = stubbingOutDataProvidersAndScheduler().with(dataProvider).build(); + + val description = cache.getMarketDescriptor( + ODD_EVEN_MARKET_ID, + IRRELEVANT, + new Languages.BestEffort(languageB, languageA) + ); + + assertThat(description.getName(languageB)).isNull(); + assertThat(description.getName(languageA)) + .isEqualTo(oddEvenMarketDescription(languageA).getName()); + } + + private void expectNotToFind(VoidCallables.ThrowingRunnable runnable) { + assertThatThrownBy(runnable::run).isInstanceOf(CacheItemNotFoundException.class); + } + } +} + +class Sources { + + public static Stream nullifyOrEmptyMarketName() { + return Stream.of( + Arguments.of(Named.of("nullified market name", Sources::nullifyMarketName)), + Arguments.of(Named.of("empty market name", Sources::emptyMarketName)) + ); + } + + public static DescMarket nullifyMarketName(DescMarket market) { + market.setName(null); + return market; + } + + public static DescMarket emptyMarketName(DescMarket market) { + market.setName(""); + return market; + } + + public interface NameRemover { + DescMarket remove(DescMarket market); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCaches.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCaches.java new file mode 100644 index 00000000..d8a6a4e6 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/InvariantMarketDescriptionCaches.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.markets; + +import static com.sportradar.unifiedodds.sdk.caching.markets.DataProviderAnswers.withGetDataThrowingByDefault; +import static com.sportradar.utils.domain.names.Languages.anyLanguages; +import static java.util.Optional.ofNullable; +import static org.mockito.Mockito.mock; + +import com.google.common.cache.CacheBuilder; +import com.sportradar.uf.sportsapi.datamodel.MarketDescriptions; +import com.sportradar.unifiedodds.sdk.impl.DataProvider; +import com.sportradar.unifiedodds.sdk.impl.ObservableDataProvider; +import com.sportradar.unifiedodds.sdk.impl.SdkTaskScheduler; +import com.sportradar.unifiedodds.sdk.impl.markets.mappings.MappingValidatorFactoryImpl; + +public class InvariantMarketDescriptionCaches { + + public static InvariantMarketDescriptionCachesBuilder stubbingOutDataProvidersAndScheduler() { + return new InvariantMarketDescriptionCachesBuilder(); + } + + public static class InvariantMarketDescriptionCachesBuilder { + + private DataProvider dataProvider; + + public InvariantMarketDescriptionCachesBuilder with(DataProvider provider) { + this.dataProvider = provider; + return this; + } + + public InvariantMarketDescriptionCache build() { + return new InvariantMarketDescriptionCache( + CacheBuilder.newBuilder().build(), + ofNullable(dataProvider).orElse(mock(DataProvider.class, withGetDataThrowingByDefault())), + mock(ObservableDataProvider.class), + new MappingValidatorFactoryImpl(), + mock(SdkTaskScheduler.class), + anyLanguages() + ); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptions.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionFactory.java similarity index 91% rename from sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptions.java rename to sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionFactory.java index 1984e349..ccdc1a84 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptions.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionFactory.java @@ -3,9 +3,6 @@ */ package com.sportradar.unifiedodds.sdk.caching.markets; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; - import com.sportradar.uf.sportsapi.datamodel.DescMarket; import com.sportradar.uf.sportsapi.datamodel.DescOutcomes; import com.sportradar.unifiedodds.sdk.domain.language.Translations; @@ -19,12 +16,12 @@ import lombok.val; import lombok.var; -public final class MarketDescriptions { +public final class MarketDescriptionFactory { - private MarketDescriptions() {} + private MarketDescriptionFactory() {} public static MarketDescription namesOf(DescMarket sapiMarketDesc, LanguageHolder language) { - return MarketDescriptions.NameFocused.singleMarketFrom(Maps.of(language.get(), sapiMarketDesc)); + return MarketDescriptionFactory.NameFocused.singleMarketFrom(Maps.of(language.get(), sapiMarketDesc)); } public static MarketDescription namesOf( @@ -33,11 +30,15 @@ public static MarketDescription namesOf( DescMarket sapiMarketDesc2, LanguageHolder language2 ) { - return MarketDescriptions.NameFocused.singleMarketFrom( + return MarketDescriptionFactory.NameFocused.singleMarketFrom( Maps.of(language1.get(), sapiMarketDesc1, language2.get(), sapiMarketDesc2) ); } + public static OutcomeDescription getOutcomeDescription(String outcomeId, MarketDescription description) { + return description.getOutcomes().stream().filter(o -> o.getId().equals(outcomeId)).findFirst().get(); + } + static class NameFocused { public static MarketDescriptionStub singleMarketFrom(Map translatedMarket) { diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviderImplTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviderImplTest.java new file mode 100644 index 00000000..c0963a29 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviderImplTest.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.markets; + +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionProviders.subbingOutCaches; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.ExactGoals.exactGoalsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.FreeTextMarketDescription.freeTextMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.NascarOutrights.nascarOutrightsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiVariantDescriptions.ExactGoals.fivePlusVariantDescription; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds.EXACT_GOALS_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds.fivePlusVariant; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.FREE_TEXT_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.nascarOutrightsVariant; +import static com.sportradar.unifiedodds.sdk.impl.MarketDescriptionDataProviders.providing; +import static com.sportradar.unifiedodds.sdk.impl.MarketDescriptionDataProviders.providingList; +import static com.sportradar.unifiedodds.sdk.impl.VariantDescriptionDataProviders.providingList; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Prepositions.of; +import static com.sportradar.utils.domain.names.LanguageHolder.in; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.Locale.ENGLISH; +import static java.util.Locale.FRENCH; +import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.collect.ImmutableMap; +import com.sportradar.uf.sportsapi.datamodel.DescOutcomes; +import com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds; +import com.sportradar.unifiedodds.sdk.entities.markets.OutcomeDescription; +import com.sportradar.unifiedodds.sdk.impl.UnifiedFeedConstants; +import com.sportradar.utils.domain.specifiers.MarketSpecifiers; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import lombok.val; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class MarketDescriptionProviderImplTest { + + @Nested + class GetMarketDescription { + + @Nested + class InvariantMarket { + + @Test + void translatesInvariantMarketName() throws Exception { + Locale aLanguage = ENGLISH; + + val provider = subbingOutCaches() + .withInvariantMarketCache( + providingList(in(aLanguage), oddEvenMarketDescription(aLanguage)) + ) + .build(); + + val actual = provider.getMarketDescription( + OddEvenMarketIds.ODD_EVEN_MARKET_ID, + emptyMap(), + singletonList(aLanguage), + false + ); + + assertThat(actual.getName(aLanguage)) + .isEqualTo(oddEvenMarketDescription(aLanguage).getName()); + } + } + + @Nested + class VariantMarket { + + @Test + void translatesStructuredVariantMarketName() throws Exception { + Locale aLanguage = FRENCH; + + val provider = subbingOutCaches() + .withInvariantMarketCache( + providingList(in(aLanguage), exactGoalsMarketDescription(aLanguage)) + ) + .withVariantCache(providingList(in(aLanguage), fivePlusVariantDescription())) + .build(); + + val actual = provider.getMarketDescription( + EXACT_GOALS_MARKET_ID, + MarketSpecifiers.variant(fivePlusVariant()), + singletonList(aLanguage), + true + ); + + assertThat(actual.getName(aLanguage)) + .isEqualTo(exactGoalsMarketDescription(aLanguage).getName()); + } + + @Test + void translatesStructuredVariantMarketNameEvenIfVariantIsMissing() throws Exception { + Locale aLanguage = FRENCH; + + val provider = subbingOutCaches() + .withInvariantMarketCache( + providingList(in(aLanguage), exactGoalsMarketDescription(aLanguage)) + ) + .build(); + + val actual = provider.getMarketDescription( + EXACT_GOALS_MARKET_ID, + MarketSpecifiers.variant(fivePlusVariant()), + singletonList(aLanguage), + true + ); + + assertThat(actual.getName(aLanguage)) + .isEqualTo(exactGoalsMarketDescription(aLanguage).getName()); + } + + @Test + void translatesUnstructuredVariantMarketNameForFreeTextMarket() throws Exception { + Locale aLanguage = ENGLISH; + + val provider = subbingOutCaches() + .withInvariantMarketCache(providingList(in(aLanguage), freeTextMarketDescription())) + .withVariantMarketCache( + providing( + in(aLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription()) + ) + ) + .build(); + + val actual = provider.getMarketDescription( + FREE_TEXT_MARKET_ID, + ImmutableMap.of( + UnifiedFeedConstants.VARIANT_DESCRIPTION_NAME, + nascarOutrightsMarketDescription().getVariant() + ), + singletonList(aLanguage), + true + ); + + assertThat(actual.getName(aLanguage)).isEqualTo(nascarOutrightsMarketDescription().getName()); + } + + @Test + void translatesUnstructuredVariantMarketNameProvidingBestEffortResponseWhenOneLocaleMissingInBatch() + throws Exception { + Locale existingVariantLanguage = ENGLISH; + Locale missingVariantLanguage = FRENCH; + + val provider = subbingOutCaches() + .withInvariantMarketCache( + providingList( + in(existingVariantLanguage), + freeTextMarketDescription(), + in(missingVariantLanguage), + freeTextMarketDescription() + ) + ) + .withVariantMarketCache( + providing( + in(existingVariantLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription()) + ) + ) + .build(); + + provider.getMarketDescription( + FREE_TEXT_MARKET_ID, + ImmutableMap.of( + UnifiedFeedConstants.VARIANT_DESCRIPTION_NAME, + nascarOutrightsMarketDescription().getVariant() + ), + singletonList(existingVariantLanguage), + true + ); + + val description = provider.getMarketDescription( + FREE_TEXT_MARKET_ID, + ImmutableMap.of( + UnifiedFeedConstants.VARIANT_DESCRIPTION_NAME, + nascarOutrightsMarketDescription().getVariant() + ), + asList(missingVariantLanguage, existingVariantLanguage), + true + ); + + assertThat(description.getName(existingVariantLanguage)) + .isEqualTo(nascarOutrightsMarketDescription().getName()); + assertThat(description.getName(missingVariantLanguage)).isNull(); + } + + @Test + @SuppressWarnings("LineLength") + void translateUnstructuredVariantMarketNameWithInvariantNameOnFreshCacheAnyLanguageInBatchIsMissingVariant() + throws Exception { + Locale existingVariantLanguage = ENGLISH; + Locale missingVariantLanguage = FRENCH; + + val provider = subbingOutCaches() + .withInvariantMarketCache( + providingList( + in(existingVariantLanguage), + freeTextMarketDescription(), + in(missingVariantLanguage), + freeTextMarketDescription() + ) + ) + .withVariantMarketCache( + providing( + in(existingVariantLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription()) + ) + ) + .build(); + + val description = provider.getMarketDescription( + FREE_TEXT_MARKET_ID, + ImmutableMap.of( + UnifiedFeedConstants.VARIANT_DESCRIPTION_NAME, + nascarOutrightsMarketDescription().getVariant() + ), + asList(missingVariantLanguage, existingVariantLanguage), + true + ); + + assertThat(description.getName(existingVariantLanguage)) + .isEqualTo(freeTextMarketDescription().getName()); + assertThat(description.getName(missingVariantLanguage)) + .isEqualTo(freeTextMarketDescription().getName()); + } + + @Test + void providesOutcomesOfPreviouslyCachedUnstructuredVariantMarketEvenIfLocaleDoesNotMatch() + throws Exception { + Locale existingVariantLanguage = ENGLISH; + Locale missingVariantLanguage = FRENCH; + + val provider = subbingOutCaches() + .withInvariantMarketCache( + providingList( + in(existingVariantLanguage), + freeTextMarketDescription(), + in(missingVariantLanguage), + freeTextMarketDescription() + ) + ) + .withVariantMarketCache( + providing( + in(existingVariantLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription()) + ) + ) + .build(); + + provider.getMarketDescription( + FREE_TEXT_MARKET_ID, + ImmutableMap.of( + UnifiedFeedConstants.VARIANT_DESCRIPTION_NAME, + nascarOutrightsMarketDescription().getVariant() + ), + singletonList(existingVariantLanguage), + true + ); + + val description = provider.getMarketDescription( + FREE_TEXT_MARKET_ID, + ImmutableMap.of( + UnifiedFeedConstants.VARIANT_DESCRIPTION_NAME, + nascarOutrightsMarketDescription().getVariant() + ), + singletonList(missingVariantLanguage), + true + ); + + assertThat(setOfIds(description.getOutcomes())) + .isEqualTo(setOfIds(nascarOutrightsMarketDescription().getOutcomes())); + } + + private Set setOfIds(List outcomes) { + return outcomes.stream().map(OutcomeDescription::getId).collect(toSet()); + } + + private Set setOfIds(DescOutcomes outcomes) { + return outcomes.getOutcome().stream().map(DescOutcomes.Outcome::getId).collect(toSet()); + } + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviders.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviders.java new file mode 100644 index 00000000..370522d3 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/MarketDescriptionProviders.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.markets; + +import static java.util.Optional.ofNullable; +import static org.mockito.Mockito.mock; + +import com.sportradar.uf.sportsapi.datamodel.MarketDescriptions; +import com.sportradar.uf.sportsapi.datamodel.VariantDescriptions; +import com.sportradar.unifiedodds.sdk.impl.DataProvider; +import java.util.function.Function; +import lombok.val; + +public class MarketDescriptionProviders { + + public static MarketDescriptionProvidersBuilder subbingOutCaches() { + return new MarketDescriptionProvidersBuilder(); + } + + public static class MarketDescriptionProvidersBuilder { + + private DataProvider variantCacheDataProvider; + private DataProvider variantMarketCacheDataProvider; + private DataProvider invariantMarketCacheDataProvider; + + public MarketDescriptionProvidersBuilder withVariantCache( + DataProvider provider + ) { + this.variantCacheDataProvider = provider; + return this; + } + + public MarketDescriptionProvidersBuilder withVariantMarketCache( + DataProvider provider + ) { + this.variantMarketCacheDataProvider = provider; + return this; + } + + public MarketDescriptionProvidersBuilder withInvariantMarketCache( + DataProvider provider + ) { + this.invariantMarketCacheDataProvider = provider; + return this; + } + + public MarketDescriptionProviderImpl build() { + val invariantMarketCache = InvariantMarketDescriptionCaches.stubbingOutDataProvidersAndScheduler(); + val variantMarketCache = VariantMarketDescriptionCaches.stubbingOutDataProvidersAndTime(); + val variantDescriptionCache = VariantDescriptionCaches.stubbingOutDataProvidersAndScheduler(); + + ofNullable(invariantMarketCacheDataProvider).ifPresent(invariantMarketCache::with); + ofNullable(variantMarketCacheDataProvider).ifPresent(variantMarketCache::with); + ofNullable(variantCacheDataProvider).ifPresent(variantDescriptionCache::with); + + return new MarketDescriptionProviderImpl( + invariantMarketCache.build(), + variantMarketCache.build(), + variantDescriptionCache.build() + ); + } + + private static Function, VariantDescriptionCache> toVariantCache() { + return p -> VariantDescriptionCaches.stubbingOutDataProvidersAndScheduler().with(p).build(); + } + + @SuppressWarnings("LineLength") + private static Function, VariantMarketDescriptionCache> toVariantMarketCache() { + return p -> VariantMarketDescriptionCaches.stubbingOutDataProvidersAndTime().with(p).build(); + } + + @SuppressWarnings("LineLength") + private static Function, InvariantMarketDescriptionCache> toInvariantMarketCache() { + return p -> + InvariantMarketDescriptionCaches.stubbingOutDataProvidersAndScheduler().with(p).build(); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantDescriptionCaches.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantDescriptionCaches.java new file mode 100644 index 00000000..46ab885c --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantDescriptionCaches.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.markets; + +import static com.sportradar.unifiedodds.sdk.caching.markets.DataProviderAnswers.withGetDataThrowingByDefault; +import static com.sportradar.utils.domain.names.Languages.anyLanguages; +import static java.util.Optional.ofNullable; +import static org.mockito.Mockito.mock; + +import com.google.common.cache.CacheBuilder; +import com.sportradar.uf.sportsapi.datamodel.VariantDescriptions; +import com.sportradar.unifiedodds.sdk.impl.DataProvider; +import com.sportradar.unifiedodds.sdk.impl.SdkTaskScheduler; +import com.sportradar.unifiedodds.sdk.impl.markets.mappings.MappingValidatorFactoryImpl; + +public class VariantDescriptionCaches { + + public static VariantDescriptionCachesBuilder stubbingOutDataProvidersAndScheduler() { + return new VariantDescriptionCachesBuilder(); + } + + public static class VariantDescriptionCachesBuilder { + + private DataProvider dataProvider; + + public VariantDescriptionCachesBuilder with(DataProvider provider) { + this.dataProvider = provider; + return this; + } + + public VariantDescriptionCache build() { + return new VariantDescriptionCacheImpl( + CacheBuilder.newBuilder().build(), + ofNullable(dataProvider).orElse(mock(DataProvider.class, withGetDataThrowingByDefault())), + new MappingValidatorFactoryImpl(), + mock(SdkTaskScheduler.class), + anyLanguages() + ); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCacheTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCacheTest.java new file mode 100644 index 00000000..94164f8e --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCacheTest.java @@ -0,0 +1,1602 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.markets; + +import static com.sportradar.unifiedodds.sdk.caching.markets.DataProviderAnswers.withGetDataThrowingByDefault; +import static com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketDescriptionCaches.stubbingOutDataProvidersAndTime; +import static com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketSources.nullifyMarketName; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.ExactGoals.exactGoalsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.NascarOutrights.*; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.NflAfcConferenceOutrights.nflAfcConferenceOutrightsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.NflAfcConferenceOutrights.openMarket; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.notFoundWithEmptyMarket; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ChampionshipFreeTextMarketIds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds.EXACT_GOALS_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds.fivePlusVariant; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.nascarOutrightsVariant; +import static com.sportradar.unifiedodds.sdk.impl.MarketDescriptionDataProviders.providing; +import static com.sportradar.unifiedodds.sdk.impl.MarketDescriptionDataProviders.providingList; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Conjunctions.and; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Determiners.every; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Prepositions.of; +import static com.sportradar.utils.domain.names.LanguageHolder.in; +import static com.sportradar.utils.time.TimeInterval.seconds; +import static java.util.Locale.ENGLISH; +import static java.util.Locale.FRENCH; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableSet; +import com.sportradar.uf.sportsapi.datamodel.DescMarket; +import com.sportradar.uf.sportsapi.datamodel.DescOutcomes; +import com.sportradar.uf.sportsapi.datamodel.MarketDescriptions; +import com.sportradar.unifiedodds.sdk.caching.ci.markets.MarketDescriptionCi; +import com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketDescriptionCache.Config; +import com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketSources.AttributeRemover; +import com.sportradar.unifiedodds.sdk.conn.MarketVariant; +import com.sportradar.unifiedodds.sdk.domain.language.Languages; +import com.sportradar.unifiedodds.sdk.entities.markets.MarketDescription; +import com.sportradar.unifiedodds.sdk.exceptions.internal.CacheItemNotFoundException; +import com.sportradar.unifiedodds.sdk.impl.DataProvider; +import com.sportradar.unifiedodds.sdk.impl.TimeUtils; +import com.sportradar.unifiedodds.sdk.impl.markets.MappingValidatorFactory; +import com.sportradar.unifiedodds.sdk.testutil.generic.concurrent.AtomicActionPerformer; +import com.sportradar.unifiedodds.sdk.testutil.generic.concurrent.VoidCallables; +import com.sportradar.utils.time.TimeInterval; +import com.sportradar.utils.time.TimeUtilsStub; +import java.time.Instant; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.*; +import java.util.function.Function; +import java.util.stream.Stream; +import lombok.SneakyThrows; +import lombok.val; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class VariantMarketDescriptionCacheTest { + + private static final int SECONDS_IN_HOUR = 60 * 60; + + private static final String NULLIFY_OR_EMPTY_MARKET_NAME = + "com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketSources#nullifyOrEmptyMarketName"; + + private static final String NULLIFY_OR_EMPTY_OUTCOMES = + "com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketSources#nullifyOrEmptyOutcomes"; + + private static final String WITH_MEMORY_CLEANUP_AND_WITHOUT = + "com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketSources#withMemoryCleanupAndWithout"; + + @SuppressWarnings("ConstantName") + private static final String NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT = + "com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketSources" + + "#nullifyOrEmptyMarketNameAndWithMemoryCleanupAndWithout"; + + private static final String LANGUAGES = + "com.sportradar.unifiedodds.sdk.caching.markets.LanguageSources#languages"; + + private static final TimeInterval FAULTY_MARKET_TTL = seconds(30); + + private static final TimeInterval LESS_THAN_FAULTY_MARKET_TTL = FAULTY_MARKET_TTL.minus(seconds(1)); + + private static final TimeInterval MORE_THAN_FAULTY_MARKET_TTL = FAULTY_MARKET_TTL.plus(seconds(1)); + + private static final long MIDNIGHT_TIMESTAMP_MILLIS = 1664402400000L; + private static final int MILLIS_IN_SECOND = 1000; + + private final TimeUtilsStub time = TimeUtilsStub + .threadSafe(new AtomicActionPerformer()) + .withCurrentTime(Instant.ofEpochMilli(MIDNIGHT_TIMESTAMP_MILLIS)); + + @Test + public void rejectsCreationWithNullArguments() { + Cache c = CacheBuilder.newBuilder().build(); + DataProvider p = mock(DataProvider.class); + MappingValidatorFactory v = mock(MappingValidatorFactory.class); + TimeUtils t = mock(TimeUtils.class); + Config f = mock(Config.class); + + assertThatThrownBy(() -> new VariantMarketDescriptionCache(null, p, v, t, f)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new VariantMarketDescriptionCache(c, null, v, t, f)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new VariantMarketDescriptionCache(c, p, null, t, f)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new VariantMarketDescriptionCache(c, p, v, null, f)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new VariantMarketDescriptionCache(c, p, v, t, null)) + .isInstanceOf(NullPointerException.class); + } + + @Nested + @SuppressWarnings({ "ClassFanOutComplexity", "MagicNumber", "LambdaBodylength" }) + class GetMarketDescriptor { + + @ParameterizedTest + @ValueSource(strings = { "en", "fr" }) + public void translatesName(String language) throws Exception { + val aLanguage = new Locale(language); + val cache = stubbingOutDataProvidersAndTime() + .with( + providing(in(aLanguage), nascarOutrightsVariant(), of(nascarOutrightsMarketDescription())) + ) + .build(); + + val description = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(description.getName(aLanguage)) + .isEqualTo(nascarOutrightsMarketDescription().getName()); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void translatesNamesToTwoDifferentLanguagesOneByOne(Config memoryCleanupSetting) + throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + val cache = stubbingOutDataProvidersAndTime() + .with( + providing( + in(languageA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageA)), + in(languageB), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageB)) + ) + ) + .with(time) + .with(memoryCleanupSetting) + .build(); + + val descriptionA = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageA) + ); + + val descriptionB = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageB) + ); + + assertThat(descriptionA.getName(languageA)) + .isEqualTo(nascarOutrightsMarketDescription(languageA).getName()); + assertThat(descriptionB.getName(languageB)) + .isEqualTo(nascarOutrightsMarketDescription(languageB).getName()); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + @SuppressWarnings({ "MagicNumber" }) + public void assertsCorrectCacheBehaviourOverLongerPeriodOfTimeForFaultyAndNonFaultyMarkets( + Config memoryCleanupSetting + ) throws Exception { + val aLanguage = ENGLISH; + DescMarket faultyOutrightMarket = nullifyMarketName( + nflAfcConferenceOutrightsMarketDescription(aLanguage) + ); + DescMarket outrightOpenMarket = openMarket(nflAfcConferenceOutrightsMarketDescription(aLanguage)); + val dataProvider = providing( + in(aLanguage), + nflAfcConferenceOutrightsVariant(), + of(faultyOutrightMarket), + in(aLanguage), + nflAfcConferenceOutrightsVariant(), + of(outrightOpenMarket) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(time) + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + final int wellWithinFaultyMarketTtl = 10; + final int wellOutsideFaultyMarketTtl = 100; + repeatFor1Hour( + doing( + () -> + cache.getMarketDescriptor( + CHAMPIONSHIP_FREE_TEXT_MARKET_ID, + nflAfcConferenceOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ), + every(seconds(wellWithinFaultyMarketTtl)) + ), + and( + doing( + () -> + cache.getMarketDescriptor( + CHAMPIONSHIP_FREE_TEXT_OPEN_MARKET_ID, + nflAfcConferenceOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ), + every(seconds(wellOutsideFaultyMarketTtl)) + ) + ) + ); + + verify(dataProvider, times(SECONDS_IN_HOUR / wellWithinFaultyMarketTtl / 4)) + .getData( + aLanguage, + CHAMPIONSHIP_FREE_TEXT_MARKET_ID + "", + nflAfcConferenceOutrightsVariant().id() + ); + verify(dataProvider, times(1)) + .getData( + aLanguage, + CHAMPIONSHIP_FREE_TEXT_OPEN_MARKET_ID + "", + nflAfcConferenceOutrightsVariant().id() + ); + } + + private void repeatFor1Hour(TimeAwareScheduledRunnable... scheduled) throws Exception { + for (int i = 0; i < SECONDS_IN_HOUR; i++) { + for (TimeAwareScheduledRunnable runnable : scheduled) { + runnable.runIfScheduled(); + } + time.tick(seconds(1)); + } + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME) + public void notTranslatesMissingNames(AttributeRemover marketName) throws Exception { + val aLanguage = ENGLISH; + + DescMarket marketDescription = marketName.remove(exactGoalsMarketDescription(aLanguage)); + + val cache = stubbingOutDataProvidersAndTime() + .with(providing(in(aLanguage), fivePlusVariant(), of(marketDescription))) + .build(); + + val description = cache.getMarketDescriptor( + EXACT_GOALS_MARKET_ID, + fivePlusVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(description.getName(aLanguage)).isNull(); + } + + @Test + public void cachesName() throws Exception { + val aLanguage = ENGLISH; + val dataProvider = providing( + in(aLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsOddEvenMarketDescription()) + ); + + val cache = stubbingOutDataProvidersAndTime().with(dataProvider).build(); + + val description1 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + val description2 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(description1.getName(aLanguage)).isEqualTo(description2.getName(aLanguage)); + verify(dataProvider, times(1)).getData(any(Locale.class), any(), any()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_OUTCOMES) + public void cachesNameForMarketWithNullOutcomes(AttributeRemover outcomes) throws Exception { + val aLanguage = ENGLISH; + val marketWithoutOutcomes = outcomes.remove(nascarOutrightsOddEvenMarketDescription()); + val dataProvider = providing(in(aLanguage), nascarOutrightsVariant(), of(marketWithoutOutcomes)); + + val cache = stubbingOutDataProvidersAndTime().with(dataProvider).build(); + + val description1 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + val description2 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(description1.getName(aLanguage)).isEqualTo(description2.getName(aLanguage)); + verify(dataProvider, times(1)).getData(any(Locale.class), any(), any()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME) + public void cachesDescriptionWithMissingNameForHalfMinute(AttributeRemover marketName) + throws Exception { + val aLanguage = ENGLISH; + val dataProvider = providing( + in(aLanguage), + fivePlusVariant(), + of(marketName.remove(exactGoalsMarketDescription(aLanguage))) + ); + val cache = stubbingOutDataProvidersAndTime().with(dataProvider).with(time).build(); + + val description1 = cache.getMarketDescriptor( + EXACT_GOALS_MARKET_ID, + fivePlusVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + time.tick(LESS_THAN_FAULTY_MARKET_TTL); + + val description2 = cache.getMarketDescriptor( + EXACT_GOALS_MARKET_ID, + fivePlusVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(description1.getName(aLanguage)).isNull(); + assertThat(description2.getName(aLanguage)).isNull(); + verify(dataProvider, times(1)) + .getData(aLanguage, EXACT_GOALS_MARKET_ID + "", fivePlusVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void evictsDescriptionWithNoNameAfterHalfMinute( + AttributeRemover marketName, + Config memoryCleanupSetting + ) throws Exception { + val aLanguage = ENGLISH; + val dataProvider = providing( + in(aLanguage), + fivePlusVariant(), + of(marketName.remove(exactGoalsMarketDescription(aLanguage))) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + val description1 = cache.getMarketDescriptor( + EXACT_GOALS_MARKET_ID, + fivePlusVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + time.tick(MORE_THAN_FAULTY_MARKET_TTL); + + val description2 = cache.getMarketDescriptor( + EXACT_GOALS_MARKET_ID, + fivePlusVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(description1.getName(aLanguage)).isNull(); + assertThat(description2.getName(aLanguage)).isNull(); + verify(dataProvider, times(2)) + .getData(aLanguage, EXACT_GOALS_MARKET_ID + "", fivePlusVariant().id()); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void descriptionsWithNameAreCachedForLongPeriodOfTime(Config memoryCleanupSetting) + throws Exception { + val aLanguage = ENGLISH; + val dataProvider = providing( + in(aLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription()) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ) + .getName(aLanguage); + + time.tick(MORE_THAN_FAULTY_MARKET_TTL); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ) + .getName(aLanguage); + + verify(dataProvider, times(1)) + .getData(aLanguage, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_OUTCOMES) + public void marketDescriptionWithoutOutcomesAtAllAreNotConsideredFaulty(AttributeRemover outcomes) + throws Exception { + val aLanguage = ENGLISH; + val dataProvider = providing( + in(aLanguage), + nascarOutrightsVariant(), + of(outcomes.remove(nascarOutrightsMarketDescription(aLanguage))) + ); + val cache = stubbingOutDataProvidersAndTime().with(dataProvider).with(time).build(); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + time.tick(MORE_THAN_FAULTY_MARKET_TTL); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + verify(dataProvider, times(1)) + .getData(aLanguage, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void descriptionWithFirstLanguagesHavingShorterAndSecondOneLongerTtls( + AttributeRemover marketName, + Config memoryCleanupSetting + ) throws Exception { + val langA = ENGLISH; + val langB = FRENCH; + DescMarket faultyMarket = marketName.remove(nascarOutrightsMarketDescription(langA)); + val dataProvider = providing( + in(langA), + nascarOutrightsVariant(), + of(faultyMarket), + in(langB), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(langB)) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ) + .getName(langA); + + time.tick(MORE_THAN_FAULTY_MARKET_TTL); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ) + .getName(langA); + + verify(dataProvider, times(2)) + .getData(langA, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + verify(dataProvider, times(1)) + .getData(langB, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void descriptionWithFirstLanguagesHavingLongerAndSecondOneShorterTtls( + AttributeRemover marketName, + Config memoryCleanupSetting + ) throws Exception { + val langA = ENGLISH; + val langB = FRENCH; + DescMarket faultyMarket = marketName.remove(nascarOutrightsMarketDescription(langB)); + val dataProvider = providing( + in(langA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(langA)), + in(langB), + nascarOutrightsVariant(), + of(faultyMarket) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ) + .getName(langA); + + time.tick(MORE_THAN_FAULTY_MARKET_TTL); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ) + .getName(langA); + + verify(dataProvider, times(1)) + .getData(langA, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + verify(dataProvider, times(2)) + .getData(langB, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void faultyMarketTtlDoesNotImpedeSecondLanguageToBeFetchedForNonFaultyMarket( + Config memoryCleanupSetting + ) throws Exception { + val langA = ENGLISH; + val langB = FRENCH; + val dataProvider = providing( + in(langA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(langA)), + in(langB), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(langB)) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA) + ) + .getName(langA); + + time.tick(MORE_THAN_FAULTY_MARKET_TTL); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langB) + ) + .getName(langA); + + verify(dataProvider, times(1)) + .getData(langA, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + verify(dataProvider, times(1)) + .getData(langB, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void faultyMarketTtlForDifferentLanguagesFetchedAtDifferentTimesShouldNotBeConflated( + AttributeRemover marketName, + Config memoryCleanupSetting + ) throws Exception { + val langA = ENGLISH; + val langB = FRENCH; + DescMarket faultyMarketLangA = marketName.remove(nascarOutrightsMarketDescription(langA)); + DescMarket faultyMarketLangB = marketName.remove(nascarOutrightsMarketDescription(langB)); + val dataProvider = providing( + in(langA), + nascarOutrightsVariant(), + of(faultyMarketLangA), + in(langB), + nascarOutrightsVariant(), + of(faultyMarketLangB) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA) + ) + .getName(langA); + + time.tick(LESS_THAN_FAULTY_MARKET_TTL); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langB) + ) + .getName(langA); + + time.tick(LESS_THAN_FAULTY_MARKET_TTL); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA) + ); + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langB) + ); + + verify(dataProvider, times(2)) + .getData(langA, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + verify(dataProvider, times(1)) + .getData(langB, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void faultyMarketTtlForRestOfLanguagesInBatchesAreIgnoredIfAtLeastOneLanguageNeedsReFetching( + AttributeRemover marketName, + Config memoryCleanupSetting + ) throws Exception { + val langA = ENGLISH; + val langB = FRENCH; + DescMarket faultyMarketLangA = marketName.remove(nascarOutrightsMarketDescription(langA)); + DescMarket faultyMarketLangB = marketName.remove(nascarOutrightsMarketDescription(langB)); + val dataProvider = providing( + in(langA), + nascarOutrightsVariant(), + of(faultyMarketLangA), + in(langB), + nascarOutrightsVariant(), + of(faultyMarketLangB) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA) + ) + .getName(langA); + + time.tick(LESS_THAN_FAULTY_MARKET_TTL); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langB) + ) + .getName(langA); + + time.tick(LESS_THAN_FAULTY_MARKET_TTL); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ); + + verify(dataProvider, times(2)) + .getData(langA, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + verify(dataProvider, times(2)) + .getData(langB, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + @SuppressWarnings("MagicNumber") + public void faultyMarketTtlIsAppliedSinceLastWriteNotLastRead( + AttributeRemover marketName, + Config memoryCleanupSetting + ) throws Exception { + val aLanguage = ENGLISH; + DescMarket faultyMarket = marketName.remove(nascarOutrightsMarketDescription(aLanguage)); + val dataProvider = providing(in(aLanguage), nascarOutrightsVariant(), of(faultyMarket)); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + repeatTimes( + 5, + () -> { + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + time.tick(LESS_THAN_FAULTY_MARKET_TTL); + } + ); + + verify(dataProvider, times(3)) + .getData(aLanguage, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + @SuppressWarnings({ "MagicNumber", "LambdaBodyLength" }) + public void faultyMarketTtlForDifferentVariantsInSingleLanguageAreNotConflated( + AttributeRemover marketName, + Config memoryCleanupSetting + ) throws Exception { + val aLanguage = ENGLISH; + DescMarket faultyWinnerMarket = marketName.remove(nascarOutrightsMarketDescription(aLanguage)); + DescMarket faultyOddEvenMarket = marketName.remove( + nascarOutrightsOddEvenMarketDescription(aLanguage) + ); + val dataProvider = providing( + in(aLanguage), + nascarOutrightsVariant(), + of(faultyWinnerMarket), + in(aLanguage), + nascarOutrightsOddEvenVariant(), + of(faultyOddEvenMarket) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + repeatTimes( + 4, + () -> { + time.tick(LESS_THAN_FAULTY_MARKET_TTL); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsOddEvenVariant().id(), + new Languages.BestEffort(aLanguage) + ); + } + ); + + verify(dataProvider, times(3)) + .getData(aLanguage, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + verify(dataProvider, times(2)) + .getData(aLanguage, FREE_TEXT_MARKET_ID + "", nascarOutrightsOddEvenVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + @SuppressWarnings({ "MagicNumber", "LambdaBodyLength" }) + public void applicationOfFaultyMarketTtlForSameVariantOfDifferentMarketsInSingleLanguageAreNotConflated( + AttributeRemover marketName, + Config memoryCleanupSetting + ) throws Exception { + val aLanguage = ENGLISH; + DescMarket faultyOutrightMarket = marketName.remove( + nflAfcConferenceOutrightsMarketDescription(aLanguage) + ); + DescMarket faultyOutrightOpenMarket = marketName.remove( + openMarket(nflAfcConferenceOutrightsMarketDescription(aLanguage)) + ); + val dataProvider = providing( + in(aLanguage), + nflAfcConferenceOutrightsVariant(), + of(faultyOutrightMarket), + in(aLanguage), + nflAfcConferenceOutrightsVariant(), + of(faultyOutrightOpenMarket) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache.getMarketDescriptor( + CHAMPIONSHIP_FREE_TEXT_MARKET_ID, + nflAfcConferenceOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + repeatTimes( + 4, + () -> { + time.tick(LESS_THAN_FAULTY_MARKET_TTL); + + cache.getMarketDescriptor( + CHAMPIONSHIP_FREE_TEXT_MARKET_ID, + nflAfcConferenceOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + cache.getMarketDescriptor( + CHAMPIONSHIP_FREE_TEXT_OPEN_MARKET_ID, + nflAfcConferenceOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + } + ); + + verify(dataProvider, times(3)) + .getData( + aLanguage, + CHAMPIONSHIP_FREE_TEXT_MARKET_ID + "", + nflAfcConferenceOutrightsVariant().id() + ); + verify(dataProvider, times(2)) + .getData( + aLanguage, + CHAMPIONSHIP_FREE_TEXT_OPEN_MARKET_ID + "", + nflAfcConferenceOutrightsVariant().id() + ); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void translatesNameToMultipleLanguagesAtOnce(Config memoryCleanupSetting) throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + val dataProvider = providing( + in(languageA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageA)), + in(languageB), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageB)) + ); + + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + val description = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageA, languageB) + ); + + assertThat(description.getName(languageA)) + .isEqualTo(nascarOutrightsMarketDescription(languageA).getName()); + assertThat(description.getName(languageB)) + .isEqualTo(nascarOutrightsMarketDescription(languageB).getName()); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void cachePreventsInvokingDataSourceRightAfterFailureToRetrieveData( + Config memoryCleanupSetting + ) throws Exception { + val aLanguage = ENGLISH; + val dataProvider = mock(DataProvider.class, withGetDataThrowingByDefault()); + + val cache = stubbingOutDataProvidersAndTime() + .with(time) + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + assertThatThrownBy(() -> + cache.getMarketDescriptor( + EXACT_GOALS_MARKET_ID, + fivePlusVariant().id(), + new Languages.BestEffort(aLanguage) + ) + ) + .isInstanceOf(CacheItemNotFoundException.class); + + assertThatThrownBy(() -> + cache.getMarketDescriptor( + EXACT_GOALS_MARKET_ID, + fivePlusVariant().id(), + new Languages.BestEffort(aLanguage) + ) + ) + .isInstanceOf(CacheItemNotFoundException.class); + verify(dataProvider, times(1)).getData(any(Locale.class), any(), any()); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void cacheProtectsDataSourceAfterFailureForSecondLanguageAfterSuccessfullyFetchingFirstOne( + Config memoryCleanupSetting + ) throws Exception { + val langA = ENGLISH; + val langB = FRENCH; + val dataProvider = providing( + in(langA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription()) + ); + + val cache = stubbingOutDataProvidersAndTime() + .with(time) + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA) + ); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langB) + ); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langB) + ); + + verify(dataProvider, times(1)) + .getData(langA, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + verify(dataProvider, times(1)) + .getData(langB, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void marketDescriptorWithCurrentlyAvailableLanguagesIsProvidedOnMissingLanguageFailureToFetch( + Config memoryCleanupSetting + ) throws Exception { + val langA = ENGLISH; + val langB = FRENCH; + val dataProvider = providing( + in(langA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(langA)) + ); + + val cache = stubbingOutDataProvidersAndTime() + .with(time) + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA) + ); + + val descriptor = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ); + + assertThat(descriptor.getName(langA)) + .isEqualTo(nascarOutrightsMarketDescription(langA).getName()); + assertThat(descriptor.getName(langB)).isNull(); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void cacheThrottlesSubsequentInvocationAfterTheFirstCallFailed(Config memoryCleanupSetting) + throws Exception { + val aLanguage = ENGLISH; + val dataProvider = mock(DataProvider.class, withGetDataThrowingByDefault()); + + val cache = stubbingOutDataProvidersAndTime() + .with(time) + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + assertThatThrownBy(() -> + cache.getMarketDescriptor( + EXACT_GOALS_MARKET_ID, + fivePlusVariant().id(), + new Languages.BestEffort(aLanguage) + ) + ) + .isInstanceOf(CacheItemNotFoundException.class); + + time.tick(MORE_THAN_FAULTY_MARKET_TTL); + + assertThatThrownBy(() -> + cache.getMarketDescriptor( + EXACT_GOALS_MARKET_ID, + fivePlusVariant().id(), + new Languages.BestEffort(aLanguage) + ) + ) + .isInstanceOf(CacheItemNotFoundException.class); + + verify(dataProvider, times(2)).getData(any(Locale.class), any(), any()); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void cacheTreatsNotFoundResponsesWithFaultyMarketsAsFaulty(Config memoryCleanupSetting) + throws Exception { + val aLanguage = ENGLISH; + val dataProvider = providing( + in(aLanguage), + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant(), + of(notFoundWithEmptyMarket()) + ); + + val cache = stubbingOutDataProvidersAndTime() + .with(time) + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + assertThatThrownBy(() -> + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ) + ) + .isInstanceOf(CacheItemNotFoundException.class); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_MARKET_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void cachesNameForLanguagesItIsAvailableInStartingWithAvailableOne( + AttributeRemover marketName, + Config memoryCleanupSetting + ) throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + + DescMarket faultyDescription = marketName.remove(nascarOutrightsMarketDescription(languageB)); + val dataProvider = providing( + in(languageA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageA)), + in(languageB), + nascarOutrightsOddEvenVariant(), + of(faultyDescription) + ); + + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + val nameInLangA = cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageA) + ) + .getName(languageA); + val noNameInLangeB = cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageB) + ) + .getName(languageB); + + assertThat(nameInLangA).isEqualTo(nascarOutrightsMarketDescription(languageA).getName()); + assertThat(noNameInLangeB).isNull(); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void cachesNameForLanguagesItIsAvailableInStartingWithMissingOne(Config memoryCleanupSetting) + throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + + DescMarket faultyDescription = nullifyMarketName(nascarOutrightsMarketDescription(languageB)); + + val dataProvider = providing( + in(languageA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageA)), + in(languageB), + nascarOutrightsVariant(), + of(faultyDescription) + ); + + val cache = stubbingOutDataProvidersAndTime() + .with(time) + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + val noNameInLangeB = cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageB) + ) + .getName(languageB); + val nameInLangA = cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageA) + ) + .getName(languageA); + + assertThat(noNameInLangeB).isNull(); + assertThat(nameInLangA).isEqualTo(nascarOutrightsMarketDescription(languageA).getName()); + } + + @Test + void concurrentNameRetrievalOfFaultyMarketsDoNotConflict() throws Exception { + val aLanguage = ENGLISH; + val executorService = Executors.newFixedThreadPool(15); + val cache = stubbingOutDataProvidersAndTime() + .with( + providing( + in(aLanguage), + nascarOutrightsVariant(), + of(nullifyMarketName(nascarOutrightsMarketDescription(aLanguage))), + in(aLanguage), + nascarOutrightsOddEvenVariant(), + of(nullifyMarketName(nascarOutrightsOddEvenMarketDescription(aLanguage))) + ) + ) + .with(aggresiveVariantCleaningConfig()) + .with(time) + .build(); + + Runnable tickClock = () -> time.tick(MORE_THAN_FAULTY_MARKET_TTL); + val fetchOutrightsMarket = createGetMarketDescriptorTask( + cache, + nascarOutrightsVariant(), + aLanguage + ); + val fetchOddEvenOutrightsMarket = createGetMarketDescriptorTask( + cache, + nascarOutrightsOddEvenVariant(), + aLanguage + ); + + repeatTimes( + 100_000, + i -> { + int marketId = FREE_TEXT_MARKET_ID + i; + executorService.submit(tickClock); + executorService.submit(fetchOutrightsMarket.apply(marketId)); + executorService.submit(fetchOddEvenOutrightsMarket.apply(marketId)); + } + ); + + repeatTimes( + 10_000, + () -> { + assertThatNoException() + .isThrownBy(() -> { + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + }); + } + ); + + executorService.shutdown(); + } + + @Nested + class LanguagesInBatch { + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void cachesNameForLanguagesItIsAvailableInStartingWithAvailableOne( + Config memoryCleanupSetting + ) throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + + DescMarket faultyDescription = nullifyMarketName(nascarOutrightsMarketDescription(languageB)); + + val dataProvider = providing( + in(languageA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageA)), + in(languageB), + nascarOutrightsVariant(), + of(faultyDescription) + ); + + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + MarketDescription desc = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageA, languageB) + ); + + assertThat(desc.getName(languageA)) + .isEqualTo(nascarOutrightsMarketDescription(languageA).getName()); + assertThat(desc.getName(languageB)).isNull(); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void cachesNameForLanguagesItIsAvailableInStartingWithMissingOne( + Config memoryCleanupSetting + ) throws Exception { + val languageA = ENGLISH; + val languageB = FRENCH; + + DescMarket faultyDescription = nullifyMarketName(nascarOutrightsMarketDescription(languageB)); + + val dataProvider = providing( + in(languageA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageA)), + in(languageB), + nascarOutrightsVariant(), + of(faultyDescription) + ); + + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(memoryCleanupSetting) + .build(); + + MarketDescription desc = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageB, languageA) + ); + + assertThat(desc.getName(languageA)) + .isEqualTo(nascarOutrightsMarketDescription(languageA).getName()); + assertThat(desc.getName(languageB)).isNull(); + } + } + + private Function createGetMarketDescriptorTask( + VariantMarketDescriptionCache cache, + MarketVariant variant, + Locale aLanguage + ) { + return marketId -> + () -> { + try { + cache.getMarketDescriptor( + marketId, + variant.id(), + new Languages.BestEffort(aLanguage) + ); + } catch (CacheItemNotFoundException e) { + // expected for markets with IDs which are not stubbed + } + }; + } + + private Config aggresiveVariantCleaningConfig() { + return new Config() { + @Override + public int getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom() { + return 0; + } + }; + } + + @SneakyThrows + private void repeatTimes(int times, VoidCallables.ThrowingRunnable r) { + for (int i = 0; i < times; i++) { + r.run(); + } + } + + @SneakyThrows + private void repeatTimes(int times, ThrowingConsumer r) { + for (int i = 0; i < times; i++) { + r.accept(i); + } + } + + @SneakyThrows + private TimeAwareScheduledRunnable doing(VoidCallables.ThrowingRunnable r, TimeInterval cadence) { + val cadenceInSeconds = toSeconds(cadence.getInMillis()); + return () -> { + long currentSeconds = toSeconds(time.now()); + if (currentSeconds % cadenceInSeconds == 0) { + r.run(); + } + }; + } + } + + @Nested + class DeleteCacheItem { + + @Test + void deletingNonexistentCacheItemHasNoSideEffect() throws Exception { + val aLanguage = ENGLISH; + val cache = stubbingOutDataProvidersAndTime() + .with( + providing( + in(aLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(aLanguage)) + ) + ) + .build(); + + cache.deleteCacheItem(FREE_TEXT_MARKET_ID, nascarOutrightsVariant().id()); + + val description = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(description.getName(aLanguage)) + .isEqualTo(nascarOutrightsMarketDescription(aLanguage).getName()); + } + + @ParameterizedTest + @MethodSource(LANGUAGES) + void removingCacheItemCausesReFetching(Locale aLanguage) throws Exception { + val dataProvider = providing( + in(aLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(aLanguage)) + ); + val cache = stubbingOutDataProvidersAndTime().with(dataProvider).build(); + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + cache.deleteCacheItem(FREE_TEXT_MARKET_ID, nascarOutrightsVariant().id()); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + verify(dataProvider, times(2)) + .getData(aLanguage, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @Test + void removesOnlyRequestedItemLeavingOthersIntact() throws Exception { + val aLanguage = ENGLISH; + val dataProvider = providing( + in(aLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(aLanguage)), + in(aLanguage), + nascarOutrightsOddEvenVariant(), + of(nascarOutrightsOddEvenMarketDescription(aLanguage)) + ); + val cache = stubbingOutDataProvidersAndTime().with(dataProvider).build(); + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsOddEvenVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + cache.deleteCacheItem(FREE_TEXT_MARKET_ID, nascarOutrightsVariant().id()); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsOddEvenVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + verify(dataProvider, times(1)) + .getData(aLanguage, FREE_TEXT_MARKET_ID + "", nascarOutrightsOddEvenVariant().id()); + } + + @Test + void removingCacheItemWithVariantIdBeingPrefixOfOtherCacheItemVariantIdDoesNotAffectThePrefixedItem() + throws Exception { + val aLanguage = ENGLISH; + String prefixVariant = "test:variant:234"; + String prefixedVariant = prefixVariant + "5"; + val dataProvider = providing( + in(aLanguage), + () -> prefixVariant, + of(testVariantMarketDescription(prefixVariant)), + in(aLanguage), + () -> prefixedVariant, + of(testVariantMarketDescription(prefixedVariant)) + ); + val cache = stubbingOutDataProvidersAndTime().with(dataProvider).build(); + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + prefixVariant, + new Languages.BestEffort(aLanguage) + ); + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + prefixedVariant, + new Languages.BestEffort(aLanguage) + ); + + cache.deleteCacheItem(FREE_TEXT_MARKET_ID, prefixVariant); + + cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + prefixedVariant, + new Languages.BestEffort(aLanguage) + ); + + verify(dataProvider, times(1)).getData(aLanguage, FREE_TEXT_MARKET_ID + "", prefixedVariant); + } + + private DescMarket testVariantMarketDescription(String variantId) { + DescMarket market = new DescMarket(); + market.setId(FREE_TEXT_MARKET_ID); + market.setVariant(variantId); + return market; + } + } + + private static long toSeconds(long millis) { + return millis / MILLIS_IN_SECOND; + } +} + +interface TimeAwareScheduledRunnable { + void runIfScheduled() throws Exception; +} + +class VariantMarketSources { + + public static Stream nullifyOrEmptyMarketNameAndWithMemoryCleanupAndWithout() { + val withMemoryCleaningUp = mock(Config.class); + when(withMemoryCleaningUp.getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom()).thenReturn(0); + val noMemoryCleaningUp = mock(Config.class); + final int highEnoughNumberToNotBeExercisedByTests = 1000; + when(noMemoryCleaningUp.getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom()) + .thenReturn(highEnoughNumberToNotBeExercisedByTests); + return Stream.of( + Arguments.of( + Named.of("nullified market name", (AttributeRemover) VariantMarketSources::nullifyMarketName), + Named.of("with memory cleanup", withMemoryCleaningUp) + ), + Arguments.of( + Named.of("empty market name", (AttributeRemover) VariantMarketSources::emptyMarketName), + Named.of("with memory cleanup", withMemoryCleaningUp) + ), + Arguments.of( + Named.of("nullified market name", (AttributeRemover) VariantMarketSources::nullifyMarketName), + Named.of("no memory cleanup", noMemoryCleaningUp) + ), + Arguments.of( + Named.of("empty market name", (AttributeRemover) VariantMarketSources::emptyMarketName), + Named.of("no memory cleanup", noMemoryCleaningUp) + ) + ); + } + + public static Stream withMemoryCleanupAndWithout() { + val configForMemoryCleaningUp = mock(Config.class); + when(configForMemoryCleaningUp.getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom()).thenReturn(0); + val configForNoMemoryCleaningUp = mock(Config.class); + final int highEnoughNumberNotToBeExercisedByTests = 1000; + when(configForMemoryCleaningUp.getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom()) + .thenReturn(highEnoughNumberNotToBeExercisedByTests); + return Stream.of( + Arguments.of(Named.of("with memory cleanup", configForMemoryCleaningUp)), + Arguments.of(Named.of("without memory cleanup", configForNoMemoryCleaningUp)) + ); + } + + @SuppressWarnings("unused") + public static Stream nullifyOrEmptyMarketName() { + return Stream.of( + Arguments.of( + Named.of("nullified market name", VariantMarketSources::nullifyMarketName) + ), + Arguments.of( + Named.of("empty market name", VariantMarketSources::emptyMarketName) + ) + ); + } + + public static DescMarket nullifyMarketName(DescMarket market) { + market.setName(null); + return market; + } + + public static DescMarket emptyMarketName(DescMarket market) { + market.setName(""); + return market; + } + + public static Stream nullifyOrEmptyOutcomes() { + return Stream.of( + Arguments.of( + Named.of("nullified outcomes", VariantMarketSources::nullifyOutcomes) + ), + Arguments.of(Named.of("zero outcomes", VariantMarketSources::emptyOutcomes)) + ); + } + + public static DescMarket nullifyOutcomes(DescMarket market) { + market.setOutcomes(null); + assertThat(market.getOutcomes()).isNull(); + return market; + } + + public static DescMarket emptyOutcomes(DescMarket market) { + market.setOutcomes(new DescOutcomes()); + assertThat(market.getOutcomes().getOutcome()).isEmpty(); + return market; + } + + public interface AttributeRemover { + DescMarket remove(DescMarket market); + } +} + +class LanguageSources { + + static Set languages() { + return ImmutableSet.of(ENGLISH, FRENCH); + } +} + +interface ThrowingConsumer { + void accept(T input) throws Exception; +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCacheUsingOutcomesTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCacheUsingOutcomesTest.java new file mode 100644 index 00000000..0c21ea0f --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCacheUsingOutcomesTest.java @@ -0,0 +1,434 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.markets; + +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionFactory.getOutcomeDescription; +import static com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketDescriptionCaches.stubbingOutDataProvidersAndTime; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.ExactGoals.exactGoalsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.NascarOutrights.nascarOutrightsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.NascarOutrights.outcomeDescriptionFor; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.getOutcomeDescription; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.FREE_TEXT_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.nascarOutrightsVariant; +import static com.sportradar.unifiedodds.sdk.impl.MarketDescriptionDataProviders.providing; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Prepositions.from; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Prepositions.of; +import static com.sportradar.utils.domain.names.LanguageHolder.in; +import static com.sportradar.utils.generic.testing.RandomObjectPicker.pickOneRandomlyFrom; +import static com.sportradar.utils.time.TimeInterval.seconds; +import static java.util.Locale.ENGLISH; +import static java.util.Locale.FRENCH; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; + +import com.sportradar.uf.sportsapi.datamodel.DescMarket; +import com.sportradar.unifiedodds.sdk.caching.markets.VariantMarketDescriptionCacheUsingOutcomesTest.TestParameterSources.OutcomeNameRemover; +import com.sportradar.unifiedodds.sdk.domain.language.Languages; +import com.sportradar.unifiedodds.sdk.testutil.generic.concurrent.AtomicActionPerformer; +import com.sportradar.utils.time.TimeInterval; +import com.sportradar.utils.time.TimeUtilsStub; +import java.time.Instant; +import java.util.Locale; +import java.util.stream.Stream; +import lombok.val; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class VariantMarketDescriptionCacheUsingOutcomesTest { + + private static final String NULLIFY_OR_EMPTY_OUTCOME_NAME = + "com.sportradar.unifiedodds.sdk.caching.markets." + + "VariantMarketDescriptionCacheUsingOutcomesTest$TestParameterSources#nullifyOrEmptyOutcomeName"; + + private static final String WITH_MEMORY_CLEANUP_AND_WITHOUT = + "com.sportradar.unifiedodds.sdk.caching.markets." + + "VariantMarketDescriptionCacheUsingOutcomesTest$TestParameterSources#withMemoryCleanupAndWithout"; + + @SuppressWarnings("ConstantName") + private static final String NULLIFY_OR_EMPTY_OUTCOME_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT = + "com.sportradar.unifiedodds.sdk.caching.markets." + + "VariantMarketDescriptionCacheUsingOutcomesTest$TestParameterSources" + + "#nullifyOrEmptyOutcomeNameAndWithMemoryCleanupAndWithout"; + + private static final TimeInterval FAULTY_MARKET_TTL = seconds(30); + + private static final TimeInterval JUST_BELOW_FAULTY_MARKET_TTL = FAULTY_MARKET_TTL.minus(seconds(1)); + + private static final TimeInterval JUST_OVER_FAULTY_MARKET_TTL = FAULTY_MARKET_TTL.plus(seconds(1)); + + private static final long MIDNIGHT_TIMESTAMP_MILLIS = 1664402400000L; + private final TimeUtilsStub time = TimeUtilsStub + .threadSafe(new AtomicActionPerformer()) + .withCurrentTime(Instant.ofEpochMilli(MIDNIGHT_TIMESTAMP_MILLIS)); + + @ParameterizedTest + @ValueSource(strings = { "en", "fr" }) + public void translatesOutcomeName(String language) throws Exception { + val outcomeId = pickOneRandomlyFrom(nascarOutrightsVariant().outcomeIds()); + + val aLanguage = new Locale(language); + val cache = stubbingOutDataProvidersAndTime() + .with(providing(in(aLanguage), nascarOutrightsVariant(), of(nascarOutrightsMarketDescription()))) + .build(); + + val description = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + val outcomeDescription = getOutcomeDescription(outcomeId, from(description)); + assertThat(outcomeDescription.getName(aLanguage)) + .isEqualTo(outcomeDescriptionFor(outcomeId).getName()); + } + + @ParameterizedTest + @MethodSource(WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void translatesOutcomeNameToTwoDifferentLanguagesOneByOne( + VariantMarketDescriptionCache.Config memoryCleanupSetting + ) throws Exception { + val outcomeId = pickOneRandomlyFrom(nascarOutrightsVariant().outcomeIds()); + val languageA = ENGLISH; + val languageB = FRENCH; + val cache = stubbingOutDataProvidersAndTime() + .with( + providing( + in(languageA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageA)), + in(languageB), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(languageB)) + ) + ) + .with(time) + .with(memoryCleanupSetting) + .build(); + + val descriptionA = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageA) + ); + + val descriptionB = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(languageB) + ); + + val outcomeDescriptionA = getOutcomeDescription(outcomeId, from(descriptionA)); + assertThat(outcomeDescriptionA.getName(languageA)) + .isEqualTo(outcomeDescriptionFor(outcomeId).getName()); + val outcomeDescriptionB = getOutcomeDescription(outcomeId, from(descriptionB)); + assertThat(outcomeDescriptionB.getName(languageB)) + .isEqualTo(outcomeDescriptionFor(outcomeId).getName()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_OUTCOME_NAME) + public void notTranslatesMissingOutcomeNames(OutcomeNameRemover outcomeName) throws Exception { + val outcomeId = pickOneRandomlyFrom(nascarOutrightsVariant().outcomeIds()); + val aLanguage = ENGLISH; + + DescMarket faultyMarketDescription = outcomeName.remove( + outcomeId, + nascarOutrightsMarketDescription(aLanguage) + ); + + val cache = stubbingOutDataProvidersAndTime() + .with(providing(in(aLanguage), nascarOutrightsVariant(), of(faultyMarketDescription))) + .build(); + + val description = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(getOutcomeDescription(outcomeId, description).getName(aLanguage)).isEmpty(); + } + + @Test + public void cachesOutcomeName() throws Exception { + val outcomeId = pickOneRandomlyFrom(nascarOutrightsVariant().outcomeIds()); + val aLanguage = ENGLISH; + val dataProvider = providing( + in(aLanguage), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(aLanguage)) + ); + + val cache = stubbingOutDataProvidersAndTime().with(dataProvider).build(); + + val description1 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + val description2 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(getOutcomeDescription(outcomeId, description1).getName(aLanguage)) + .isEqualTo(getOutcomeDescription(outcomeId, description2).getName(aLanguage)); + verify(dataProvider, times(1)).getData(any(Locale.class), any(), any()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_OUTCOME_NAME) + public void cachesDescriptionWithMissingOutcomeNameForHalfMinute(OutcomeNameRemover outcomeName) + throws Exception { + val outcomeId = pickOneRandomlyFrom(nascarOutrightsVariant().outcomeIds()); + val aLanguage = ENGLISH; + val faultyMarket = outcomeName.remove(outcomeId, nascarOutrightsMarketDescription(aLanguage)); + val dataProvider = providing(in(aLanguage), nascarOutrightsVariant(), of(faultyMarket)); + val cache = stubbingOutDataProvidersAndTime().with(dataProvider).with(time).build(); + + val description1 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + time.tick(JUST_BELOW_FAULTY_MARKET_TTL); + + val description2 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(getOutcomeDescription(outcomeId, description1).getName(aLanguage)).isEmpty(); + assertThat(getOutcomeDescription(outcomeId, description2).getName(aLanguage)).isEmpty(); + verify(dataProvider, times(1)) + .getData(aLanguage, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_OUTCOME_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void evictsDescriptionWithNoOutcomeNameAfterHalfMinute( + OutcomeNameRemover outcomeName, + VariantMarketDescriptionCache.Config memoryCleanupSetting + ) throws Exception { + val outcomeId = pickOneRandomlyFrom(nascarOutrightsVariant().outcomeIds()); + val aLanguage = ENGLISH; + val faultyMarket = outcomeName.remove(outcomeId, nascarOutrightsMarketDescription(aLanguage)); + val dataProvider = providing(in(aLanguage), nascarOutrightsVariant(), of(faultyMarket)); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + val description1 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + time.tick(JUST_OVER_FAULTY_MARKET_TTL); + + val description2 = cache.getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(aLanguage) + ); + + assertThat(getOutcomeDescription(outcomeId, description1).getName(aLanguage)).isEmpty(); + assertThat(getOutcomeDescription(outcomeId, description2).getName(aLanguage)).isEmpty(); + verify(dataProvider, times(2)) + .getData(aLanguage, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_OUTCOME_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void descriptionWithFirstLanguagesHavingShorterAndSecondOneLongerTtls( + OutcomeNameRemover outcomeName, + VariantMarketDescriptionCache.Config memoryCleanupSetting + ) throws Exception { + val outcomeId = pickOneRandomlyFrom(nascarOutrightsVariant().outcomeIds()); + val langA = ENGLISH; + val langB = FRENCH; + DescMarket faultyMarket = outcomeName.remove(outcomeId, nascarOutrightsMarketDescription(langA)); + val dataProvider = providing( + in(langA), + nascarOutrightsVariant(), + of(faultyMarket), + in(langB), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(langB)) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ) + .getName(langA); + + time.tick(JUST_OVER_FAULTY_MARKET_TTL); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ) + .getName(langA); + + verify(dataProvider, times(2)) + .getData(langA, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + verify(dataProvider, times(1)) + .getData(langB, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + @ParameterizedTest + @MethodSource(NULLIFY_OR_EMPTY_OUTCOME_NAME__WITH_MEMORY_CLEANUP_AND_WITHOUT) + public void descriptionWithFirstLanguagesHavingLongerAndSecondOneShorterTtls( + OutcomeNameRemover outcomeName, + VariantMarketDescriptionCache.Config memoryCleanupSetting + ) throws Exception { + val outcomeId = pickOneRandomlyFrom(nascarOutrightsVariant().outcomeIds()); + val langA = ENGLISH; + val langB = FRENCH; + DescMarket faultyMarket = outcomeName.remove(outcomeId, nascarOutrightsMarketDescription(langB)); + val dataProvider = providing( + in(langA), + nascarOutrightsVariant(), + of(nascarOutrightsMarketDescription(langA)), + in(langB), + nascarOutrightsVariant(), + of(faultyMarket) + ); + val cache = stubbingOutDataProvidersAndTime() + .with(dataProvider) + .with(time) + .with(memoryCleanupSetting) + .build(); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ) + .getName(langA); + + time.tick(JUST_OVER_FAULTY_MARKET_TTL); + + cache + .getMarketDescriptor( + FREE_TEXT_MARKET_ID, + nascarOutrightsVariant().id(), + new Languages.BestEffort(langA, langB) + ) + .getName(langA); + + verify(dataProvider, times(1)) + .getData(langA, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + verify(dataProvider, times(2)) + .getData(langB, FREE_TEXT_MARKET_ID + "", nascarOutrightsVariant().id()); + } + + public static class TestParameterSources { + + public static Stream nullifyOrEmptyOutcomeNameAndWithMemoryCleanupAndWithout() { + val withMemoryCleaningUp = mock(VariantMarketDescriptionCache.Config.class); + when(withMemoryCleaningUp.getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom()).thenReturn(0); + val noMemoryCleaningUp = mock(VariantMarketDescriptionCache.Config.class); + final int highEnoughNumberToNotBeExercisedByTests = 1000; + when(noMemoryCleaningUp.getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom()) + .thenReturn(highEnoughNumberToNotBeExercisedByTests); + return Stream.of( + Arguments.of( + Named.of( + "nullified market name", + (OutcomeNameRemover) TestParameterSources::nullifyOutcomeName + ), + Named.of("with memory cleanup", withMemoryCleaningUp) + ), + Arguments.of( + Named.of( + "empty market name", + (OutcomeNameRemover) TestParameterSources::emptyOutcomeName + ), + Named.of("with memory cleanup", withMemoryCleaningUp) + ), + Arguments.of( + Named.of( + "nullified market name", + (OutcomeNameRemover) TestParameterSources::nullifyOutcomeName + ), + Named.of("no memory cleanup", noMemoryCleaningUp) + ), + Arguments.of( + Named.of( + "empty market name", + (OutcomeNameRemover) TestParameterSources::emptyOutcomeName + ), + Named.of("no memory cleanup", noMemoryCleaningUp) + ) + ); + } + + public static Stream withMemoryCleanupAndWithout() { + val configForMemoryCleaningUp = mock(VariantMarketDescriptionCache.Config.class); + when(configForMemoryCleaningUp.getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom()) + .thenReturn(0); + val configForNoMemoryCleaningUp = mock(VariantMarketDescriptionCache.Config.class); + final int highEnoughNumberNotToBeExercisedByTests = 1000; + when(configForMemoryCleaningUp.getNumberOfVariantsToStartAggressiveCleanUpMemoryFrom()) + .thenReturn(highEnoughNumberNotToBeExercisedByTests); + return Stream.of( + Arguments.of(Named.of("with memory cleanup", configForMemoryCleaningUp)), + Arguments.of(Named.of("without memory cleanup", configForNoMemoryCleaningUp)) + ); + } + + @SuppressWarnings("unused") + public static Stream nullifyOrEmptyOutcomeName() { + return Stream.of( + Arguments.of( + Named.of( + "nullified outcome name", + TestParameterSources::nullifyOutcomeName + ) + ), + Arguments.of( + Named.of("empty outcome name", TestParameterSources::emptyOutcomeName) + ) + ); + } + + public static DescMarket nullifyOutcomeName(String outcomeId, DescMarket market) { + getOutcomeDescription(outcomeId, market).setName(null); + return market; + } + + public static DescMarket emptyOutcomeName(String outcomeId, DescMarket market) { + getOutcomeDescription(outcomeId, market).setName(""); + return market; + } + + public interface OutcomeNameRemover { + DescMarket remove(String outcomeId, DescMarket market); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCaches.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCaches.java new file mode 100644 index 00000000..a78afce1 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/markets/VariantMarketDescriptionCaches.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.markets; + +import static com.sportradar.unifiedodds.sdk.caching.markets.DataProviderAnswers.withGetDataThrowingByDefault; +import static java.util.Optional.ofNullable; +import static org.mockito.Mockito.mock; + +import com.google.common.cache.CacheBuilder; +import com.sportradar.uf.sportsapi.datamodel.MarketDescriptions; +import com.sportradar.unifiedodds.sdk.impl.DataProvider; +import com.sportradar.unifiedodds.sdk.impl.TimeUtils; +import com.sportradar.unifiedodds.sdk.impl.markets.mappings.MappingValidatorFactoryImpl; +import java.util.Optional; + +public class VariantMarketDescriptionCaches { + + public static VariantMarketDescriptionCachesBuilder stubbingOutDataProvidersAndTime() { + return new VariantMarketDescriptionCachesBuilder(); + } + + public static class VariantMarketDescriptionCachesBuilder { + + private DataProvider dataProvider; + + private Optional time = Optional.empty(); + + private Optional config = Optional.empty(); + + public VariantMarketDescriptionCachesBuilder with(DataProvider provider) { + this.dataProvider = provider; + return this; + } + + public VariantMarketDescriptionCachesBuilder with(TimeUtils timeUtils) { + this.time = Optional.of(timeUtils); + return this; + } + + public VariantMarketDescriptionCachesBuilder with( + VariantMarketDescriptionCache.Config configuration + ) { + this.config = Optional.of(configuration); + return this; + } + + public VariantMarketDescriptionCache build() { + return new VariantMarketDescriptionCache( + CacheBuilder.newBuilder().build(), + ofNullable(dataProvider).orElse(mock(DataProvider.class, withGetDataThrowingByDefault())), + new MappingValidatorFactoryImpl(), + time.orElse(mock(TimeUtils.class)), + config.orElse(mock(VariantMarketDescriptionCache.Config.class)) + ); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/SdkConfigurationReaderStubs.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/SdkConfigurationReaderStubs.java new file mode 100644 index 00000000..0d54fa5f --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/SdkConfigurationReaderStubs.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.cfg; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.sportradar.utils.domain.config.Tokens; +import com.sportradar.utils.domain.names.Languages; +import java.util.Optional; +import lombok.val; + +class SdkConfigurationReaderStubs { + + public static class Props { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Optional environment = Optional.empty(); + + @SuppressWarnings("HiddenField") + public Builder with(Environment environment) { + this.environment = Optional.of(environment); + return this; + } + + public SdkConfigurationPropertiesReader build() { + val reader = mock(SdkConfigurationPropertiesReader.class); + when(reader.readEnvironment()).thenReturn(environment.orElse(Environments.any())); + when(reader.readAccessToken()).thenReturn(Optional.of(Tokens.any())); + when(reader.readDefaultLanguage()).thenReturn(Optional.of(Languages.any())); + return reader; + } + } + } + + public static class Yaml { + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Optional environment = Optional.empty(); + + @SuppressWarnings("HiddenField") + public Builder with(Environment environment) { + this.environment = Optional.of(environment); + return this; + } + + public SdkConfigurationYamlReader build() { + val reader = mock(SdkConfigurationYamlReader.class); + when(reader.readEnvironment()).thenReturn(environment.orElse(Environments.any())); + when(reader.readAccessToken()).thenReturn(Optional.of(Tokens.any())); + when(reader.readDefaultLanguage()).thenReturn(Optional.of(Languages.any())); + return reader; + } + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/UofConfigurationTests.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/UofConfigurationTests.java index ddfb94f7..bdafb753 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/UofConfigurationTests.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/UofConfigurationTests.java @@ -4,14 +4,23 @@ package com.sportradar.unifiedodds.sdk.cfg; +import static com.sportradar.unifiedodds.sdk.cfg.Environment.GlobalReplay; +import static com.sportradar.unifiedodds.sdk.cfg.Environment.Replay; +import static com.sportradar.unifiedodds.sdk.cfg.UofConfigurations.BuilderViaFileStubbingOutDataProvidersAndReaders.viaFileStubbingOutDataProvidersAndReaders; +import static com.sportradar.unifiedodds.sdk.cfg.UofConfigurations.BuilderViaJavaStubbingOutDataProvidersAndReaders.viaJavaStubbingOutDataProvidersAndReaders; +import static com.sportradar.unifiedodds.sdk.impl.BookmakerDetailsDataProvider.providing; import static com.sportradar.unifiedodds.sdk.impl.ProducerDataProviderStubs.providerOfSingleEmptyProducer; import static com.sportradar.unifiedodds.sdk.impl.apireaders.WhoAmIReaderStubs.emptyBookmakerDetailsReader; import static com.sportradar.unifiedodds.sdk.impl.apireaders.WhoAmIReaderStubs.readerProvidingBookmaker; +import static com.sportradar.utils.domain.names.LanguageHolder.in; +import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.conn.SapiBookmakerDetails; import com.sportradar.unifiedodds.sdk.entities.BookmakerDetails; +import com.sportradar.unifiedodds.sdk.exceptions.internal.DataProviderException; import com.sportradar.unifiedodds.sdk.impl.EnvironmentManager; import com.sportradar.unifiedodds.sdk.impl.ProducerDataProvider; import com.sportradar.unifiedodds.sdk.impl.apireaders.WhoAmIReader; @@ -117,7 +126,7 @@ public void setDefaultLanguage_LastValue() { @Test public void setLanguages() { - final List newValue = Arrays.asList(Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN); + final List newValue = Arrays.asList(ENGLISH, Locale.FRENCH, Locale.GERMAN); config.setLanguages(newValue); Assert.assertNotNull(config.getLanguages()); @@ -126,7 +135,7 @@ public void setLanguages() { @Test public void setLanguagesSavesOnlyUniqueValues() { - final List newValue = Arrays.asList(Locale.ENGLISH, Locale.ENGLISH, Locale.ENGLISH); + final List newValue = Arrays.asList(ENGLISH, ENGLISH, ENGLISH); config.setLanguages(newValue); Assert.assertEquals(1, config.getLanguages().size()); @@ -134,7 +143,7 @@ public void setLanguagesSavesOnlyUniqueValues() { @Test public void setLanguagesSavesOnlyUniqueValuesWhenCalledMultipleTimes() { - final List newValue = Arrays.asList(Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN); + final List newValue = Arrays.asList(ENGLISH, Locale.FRENCH, Locale.GERMAN); config.setLanguages(newValue); config.setLanguages(newValue); @@ -143,7 +152,7 @@ public void setLanguagesSavesOnlyUniqueValuesWhenCalledMultipleTimes() { @Test public void setLanguagesCanNotRemovePreviouslySavedValues() { - final List newValue = Arrays.asList(Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN); + final List newValue = Arrays.asList(ENGLISH, Locale.FRENCH, Locale.GERMAN); config.setLanguages(newValue); Assert.assertEquals(newValue.size(), config.getLanguages().size()); @@ -155,7 +164,7 @@ public void setLanguagesCanNotRemovePreviouslySavedValues() { @Test public void setLanguagesOverridesNewValues() { - final List newValues = Arrays.asList(Locale.ENGLISH, Locale.GERMAN); + final List newValues = Arrays.asList(ENGLISH, Locale.GERMAN); final List newValues2 = Arrays.asList(Locale.FRENCH, Locale.GERMAN, Locale.ITALY); config.setLanguages(newValues); @@ -171,7 +180,7 @@ public void validateMinimumSettings_MissingDefaultLanguage() { final String accessToken = "myAccessToken"; config.setAccessToken(accessToken); - final List newValues = Arrays.asList(Locale.ENGLISH, Locale.GERMAN); + final List newValues = Arrays.asList(ENGLISH, Locale.GERMAN); config.setLanguages(newValues); Assert.assertEquals(newValues.size(), config.getLanguages().size()); Assert.assertNull(config.getDefaultLanguage()); @@ -188,13 +197,13 @@ public void validateMinimumSettings_MissingLanguages() { final String accessToken = "myAccessToken"; config.setAccessToken(accessToken); - config.setDefaultLanguage(Locale.ENGLISH); - Assert.assertEquals(Locale.ENGLISH, config.getDefaultLanguage()); + config.setDefaultLanguage(ENGLISH); + Assert.assertEquals(ENGLISH, config.getDefaultLanguage()); Assert.assertEquals(0, config.getLanguages().size()); config.validateMinimumSettings(); - Assert.assertEquals(Locale.ENGLISH, config.getDefaultLanguage()); + Assert.assertEquals(ENGLISH, config.getDefaultLanguage()); Assert.assertEquals(1, config.getLanguages().size()); Assert.assertEquals(config.getDefaultLanguage(), config.getLanguages().stream().findFirst().get()); } @@ -204,7 +213,7 @@ public void validateMinimumSettings_InsertingDefaultLanguage() { final String accessToken = "myAccessToken"; config.setAccessToken(accessToken); - final List newValues = Arrays.asList(Locale.ENGLISH, Locale.GERMAN); + final List newValues = Arrays.asList(ENGLISH, Locale.GERMAN); final Locale defaultLocale = Locale.FRENCH; config.setLanguages(newValues); config.setDefaultLanguage(defaultLocale); @@ -220,7 +229,7 @@ public void validateMinimumSettings_InsertingDefaultLanguage() { @Test(expected = InvalidParameterException.class) public void validateMinimumSettings_MissingAccessToken() { - final List newValues = Arrays.asList(Locale.ENGLISH, Locale.GERMAN); + final List newValues = Arrays.asList(ENGLISH, Locale.GERMAN); config.setLanguages(newValues); Assert.assertNull(config.getDefaultLanguage()); Assert.assertEquals(newValues.size(), config.getLanguages().size()); @@ -496,6 +505,172 @@ public void setBookmakerDetails() { ); } + @Test + public void integrationApiHostIsSetWhenIntegrationTokenIsUsedToReplay() throws DataProviderException { + val configuration = viaJavaStubbingOutDataProvidersAndReaders() + .withIntegration(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withReplay(Replay) + .build(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Integration).getApiHost()); + } + + @Test + public void productionApiHostIsSetWhenProductionTokenIsUsedToReplay() throws DataProviderException { + val configuration = viaJavaStubbingOutDataProvidersAndReaders() + .withProduction(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withReplay(Replay) + .build(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Production).getApiHost()); + } + + @Test + public void integrationApiHostIsSetWhenIntegrationTokenIsUsedToReplayViaNoArgCall() + throws DataProviderException { + val configuration = viaJavaStubbingOutDataProvidersAndReaders() + .withIntegration(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withReplay() + .build(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Integration).getApiHost()); + } + + @Test + public void productionApiHostIsSetWhenProductionTokenIsUsedToReplayViaNoArgCall() + throws DataProviderException { + val configuration = viaJavaStubbingOutDataProvidersAndReaders() + .withProduction(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withReplay() + .build(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Production).getApiHost()); + } + + @Test + public void integrationApiHostIsSetWhenIntegrationTokenIsUsedForGlobalReplay() + throws DataProviderException { + val configuration = viaJavaStubbingOutDataProvidersAndReaders() + .withIntegration(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withReplay(GlobalReplay) + .build(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Integration).getApiHost()); + } + + @Test + public void productionApiHostIsSetWhenProductionTokenIsUsedForGlobalReplay() + throws DataProviderException { + val configuration = viaJavaStubbingOutDataProvidersAndReaders() + .withProduction(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withReplay(GlobalReplay) + .build(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Production).getApiHost()); + } + + @Test + public void integrationApiHostIsSetWhenIntegrationTokenInPropsFileIsUsedToReplay() + throws DataProviderException { + val configuration = viaFileStubbingOutDataProvidersAndReaders() + .withIntegration(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withProps(p -> p.with(Replay)) + .buildFromProps(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Integration).getApiHost()); + } + + @Test + public void productionApiHostIsSetWhenProductionTokenInPropsFileIsUsedToReplay() + throws DataProviderException { + val configuration = viaFileStubbingOutDataProvidersAndReaders() + .withProduction(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withProps(p -> p.with(Replay)) + .buildFromProps(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Production).getApiHost()); + } + + @Test + public void integrationApiHostIsSetWhenIntegrationTokenInPropsFileIsUsedForGlobalReplay() + throws DataProviderException { + val configuration = viaFileStubbingOutDataProvidersAndReaders() + .withIntegration(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withProps(p -> p.with(GlobalReplay)) + .buildFromProps(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Integration).getApiHost()); + } + + @Test + public void productionApiHostIsSetWhenProductionTokenInPropsFileIsUsedForGlobalReplay() + throws DataProviderException { + val configuration = viaFileStubbingOutDataProvidersAndReaders() + .withProduction(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withProps(p -> p.with(GlobalReplay)) + .buildFromProps(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Production).getApiHost()); + } + + @Test + public void integrationApiHostIsSetWhenIntegrationTokenInYamlFileIsUsedToReplay() + throws DataProviderException { + val configuration = viaFileStubbingOutDataProvidersAndReaders() + .withIntegration(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withYaml(p -> p.with(Replay)) + .buildFromYaml(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Integration).getApiHost()); + } + + @Test + public void productionApiHostIsSetWhenProductionTokenInYamlFileIsUsedToReplay() + throws DataProviderException { + val configuration = viaFileStubbingOutDataProvidersAndReaders() + .withProduction(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withYaml(p -> p.with(Replay)) + .buildFromYaml(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Production).getApiHost()); + } + + @Test + public void integrationApiHostIsSetWhenIntegrationTokenInYamlFileIsUsedForGlobalReplay() + throws DataProviderException { + val configuration = viaFileStubbingOutDataProvidersAndReaders() + .withIntegration(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withYaml(p -> p.with(GlobalReplay)) + .buildFromYaml(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Integration).getApiHost()); + } + + @Test + public void productionApiHostIsSetWhenProductionTokenInYamlFileIsUsedForGlobalReplay() + throws DataProviderException { + val configuration = viaFileStubbingOutDataProvidersAndReaders() + .withProduction(providing(in(ENGLISH), SapiBookmakerDetails.valid())) + .withYaml(p -> p.with(GlobalReplay)) + .buildFromYaml(); + + assertThat(configuration.getApi().getHost()) + .isEqualTo(EnvironmentManager.getSetting(Environment.Production).getApiHost()); + } + @Test public void setBookmakerDetailsDoesNotOverrideVirtualHostIfPreset() { final String originalVirtualHost = "/originalVirtualHost"; diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/UofConfigurations.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/UofConfigurations.java new file mode 100644 index 00000000..fd33b81d --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/cfg/UofConfigurations.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.cfg; + +import static com.sportradar.unifiedodds.sdk.caching.markets.DataProviderAnswers.withGetDataThrowingByDefault; +import static com.sportradar.unifiedodds.sdk.impl.ProducerDataProviderStubs.providerOfSingleEmptyProducer; +import static org.mockito.Mockito.mock; + +import com.sportradar.uf.sportsapi.datamodel.BookmakerDetails; +import com.sportradar.unifiedodds.sdk.cfg.SdkConfigurationReaderStubs.Props; +import com.sportradar.unifiedodds.sdk.cfg.SdkConfigurationReaderStubs.Yaml; +import com.sportradar.unifiedodds.sdk.impl.DataProvider; +import com.sportradar.unifiedodds.sdk.impl.apireaders.WhoAmIReader; +import com.sportradar.utils.domain.config.Tokens; +import com.sportradar.utils.domain.names.Languages; +import java.util.Optional; +import java.util.function.Function; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.val; + +class UofConfigurations { + + public static class BuilderViaFileStubbingOutDataProvidersAndReaders { + + private static final DataProvider PROVIDER_THROWING_WHEN_GETTING_DATA = mock( + DataProvider.class, + withGetDataThrowingByDefault() + ); + + private Optional> integrationDataProvider = Optional.empty(); + + private Optional> productionDataProvider = Optional.empty(); + + private Function propsConfigurer = p -> p; + + private Function yamlConfigurer = p -> p; + + public static BuilderViaFileStubbingOutDataProvidersAndReaders viaFileStubbingOutDataProvidersAndReaders() { + return new BuilderViaFileStubbingOutDataProvidersAndReaders(); + } + + public BuilderViaFileStubbingOutDataProvidersAndReaders withIntegration( + DataProvider provider + ) { + this.integrationDataProvider = Optional.of(provider); + return this; + } + + public BuilderViaFileStubbingOutDataProvidersAndReaders withProduction( + DataProvider provider + ) { + this.productionDataProvider = Optional.of(provider); + return this; + } + + @SuppressWarnings("HiddenField") + public BuilderViaFileStubbingOutDataProvidersAndReaders withProps( + Function propsConfigurer + ) { + this.propsConfigurer = propsConfigurer; + return this; + } + + @SuppressWarnings("HiddenField") + public BuilderViaFileStubbingOutDataProvidersAndReaders withYaml( + Function yamlConfigurer + ) { + this.yamlConfigurer = yamlConfigurer; + return this; + } + + public UofConfiguration buildFromProps() { + return createTokenSetter().buildConfigFromSdkProperties(); + } + + public UofConfiguration buildFromYaml() { + return createTokenSetter().buildConfigFromApplicationYml(); + } + + private TokenSetterImpl createTokenSetter() { + return new TokenSetterImpl( + propsConfigurer.apply(SdkConfigurationReaderStubs.Props.builder()).build(), + yamlConfigurer.apply(SdkConfigurationReaderStubs.Yaml.builder()).build(), + config -> + new WhoAmIReader( + config, + new ApiHostUpdater((UofConfigurationImpl) config), + mock(DataProvider.class), + productionDataProvider.orElse(PROVIDER_THROWING_WHEN_GETTING_DATA), + integrationDataProvider.orElse(PROVIDER_THROWING_WHEN_GETTING_DATA) + ), + c -> providerOfSingleEmptyProducer() + ); + } + } + + public static class BuilderViaJavaStubbingOutDataProvidersAndReaders { + + private static final DataProvider PROVIDER_THROWING_WHEN_GETTING_DATA = mock( + DataProvider.class, + withGetDataThrowingByDefault() + ); + + private Optional> integrationDataProvider = Optional.empty(); + + private Optional> productionDataProvider = Optional.empty(); + + private Optional replayEnvironmentSelector = Optional.empty(); + + public static BuilderViaJavaStubbingOutDataProvidersAndReaders viaJavaStubbingOutDataProvidersAndReaders() { + return new BuilderViaJavaStubbingOutDataProvidersAndReaders(); + } + + public BuilderViaJavaStubbingOutDataProvidersAndReaders withIntegration( + DataProvider provider + ) { + this.integrationDataProvider = Optional.of(provider); + return this; + } + + public BuilderViaJavaStubbingOutDataProvidersAndReaders withProduction( + DataProvider provider + ) { + this.productionDataProvider = Optional.of(provider); + return this; + } + + @SuppressWarnings("HiddenField") + public BuilderViaJavaStubbingOutDataProvidersAndReaders withReplay(Environment environment) { + this.replayEnvironmentSelector = Optional.of(ReplayEnvironmentConfigurer.of(environment)); + return this; + } + + public BuilderViaJavaStubbingOutDataProvidersAndReaders withReplay() { + this.replayEnvironmentSelector = Optional.of(ReplayEnvironmentConfigurer.viaNoArgCall()); + return this; + } + + public UofConfiguration build() { + val environmentSelector = createTokenSetter().setAccessToken(Tokens.any()); + + ConfigurationBuilder configurationBuilder = replayEnvironmentSelector + .map(c -> c.select(environmentSelector)) + .orElseGet(() -> environmentSelector.selectEnvironment(Environments.any())); + + return configurationBuilder.setDefaultLanguage(Languages.any()).build(); + } + + private TokenSetterImpl createTokenSetter() { + return new TokenSetterImpl( + mock(SdkConfigurationPropertiesReader.class), + mock(SdkConfigurationYamlReader.class), + config -> + new WhoAmIReader( + config, + new ApiHostUpdater((UofConfigurationImpl) config), + mock(DataProvider.class), + productionDataProvider.orElse(PROVIDER_THROWING_WHEN_GETTING_DATA), + integrationDataProvider.orElse(PROVIDER_THROWING_WHEN_GETTING_DATA) + ), + c -> providerOfSingleEmptyProducer() + ); + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + private static class ReplayEnvironmentConfigurer { + + private final Environment environment; + private final boolean configViaNoArgCall; + + static ReplayEnvironmentConfigurer viaNoArgCall() { + return new ReplayEnvironmentConfigurer(null, true); + } + + static ReplayEnvironmentConfigurer of(Environment environment) { + validateIsReplay(environment); + return new ReplayEnvironmentConfigurer(environment, false); + } + + private static void validateIsReplay(Environment environment) { + if (!Environments.getReplayEnvironments().contains(environment)) { + throw new IllegalArgumentException( + "Replay Environment can be configured only with environments " + + "representing Replay, but was: " + + environment + ); + } + } + + ConfigurationBuilder select(EnvironmentSelector environmentSelector) { + if (configViaNoArgCall) { + return environmentSelector.selectReplay(); + } else { + return environmentSelector.selectEnvironment(environment); + } + } + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/ApiSimulator.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/ApiSimulator.java index dbf675a3..5de8db70 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/ApiSimulator.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/ApiSimulator.java @@ -6,23 +6,34 @@ import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.sportradar.unifiedodds.sdk.conn.ProducerId.LIVE_ODDS; import static com.sportradar.unifiedodds.sdk.conn.SapiProducers.buildActiveProducer; +import static java.lang.String.format; +import com.github.tomakehurst.wiremock.client.MappingBuilder; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; -import com.sportradar.uf.sportsapi.datamodel.DescMarket; -import com.sportradar.uf.sportsapi.datamodel.MarketDescriptions; -import com.sportradar.uf.sportsapi.datamodel.Producers; -import com.sportradar.uf.sportsapi.datamodel.ResponseCode; +import com.google.common.base.Charsets; +import com.sportradar.uf.custombet.datamodel.CapiResponse; +import com.sportradar.uf.sportsapi.datamodel.*; +import java.io.ByteArrayOutputStream; +import java.util.Locale; +import java.util.function.Consumer; +import javax.xml.bind.JAXB; +import lombok.SneakyThrows; import lombok.val; +import org.apache.http.HttpStatus; public class ApiSimulator { public static final String XML_DECLARATION = "\n"; - private final WireMockRule wireMockRule; + private final Consumer stubRegistrar; public ApiSimulator(WireMockRule wireMockRule) { - this.wireMockRule = wireMockRule; + this.stubRegistrar = wireMockRule::stubFor; + } + + public ApiSimulator(WireMock wireMock) { + this.stubRegistrar = wireMock::register; } public void activateOnlyLiveProducer() { @@ -30,14 +41,14 @@ public void activateOnlyLiveProducer() { producers.setResponseCode(ResponseCode.OK); producers.getProducer().add(buildActiveProducer(LIVE_ODDS)); - wireMockRule.stubFor( + register( get(urlPathEqualTo("/v1/descriptions/producers.xml")) .willReturn(WireMock.ok(JaxbContexts.SportsApi.marshall(producers))) ); } public void defineBookmaker() { - wireMockRule.stubFor( + register( get(urlPathEqualTo("/v1/users/whoami.xml")) .willReturn( WireMock.ok( @@ -51,25 +62,101 @@ public void defineBookmaker() { ); } - public void stubEmptyMarketList() { - stub(new MarketDescriptions()); + public void stubEmptyMarketList(Locale language) { + stub(new MarketDescriptions(), format("/v1/descriptions/%s/markets.xml.*", language.toString())); } - public void stubMarketListContaining(DescMarket market) { + public void stubMarketListContaining(DescMarket market, Locale language) { val descriptions = new MarketDescriptions(); descriptions.getMarket().add(market); - stub(descriptions); + stub(descriptions, format("/v1/descriptions/%s/markets.xml.*", language.toString())); + } + + public void stubVariantListContaining(DescVariant variantDescription, Locale language) { + val descriptions = new VariantDescriptions(); + descriptions.getVariant().add(variantDescription); + stub(descriptions, format("/v1/descriptions/%s/variants.xml.*", language.toString())); + } + + public void stubEmptyVariantList(Locale language) { + val descriptions = new VariantDescriptions(); + stub(descriptions, format("/v1/descriptions/%s/variants.xml.*", language.toString())); + } + + public void stubSingleVariantMarket(DescMarket market, Locale language) { + val descriptions = new MarketDescriptions(); + descriptions.getMarket().add(market); + stub( + descriptions, + format( + "/v1/descriptions/%s/markets/%s/variants/%s?.*", + language, + market.getId(), + market.getVariant() + ) + ); + } + + public void stubSingleVariantMarket( + int httpStatus, + int marketId, + Identifiable variantId, + Locale language + ) { + stub( + httpStatus, + format( + "/v1/descriptions/%s/markets/%s/variants/%s.*", + language.getLanguage(), + marketId, + variantId.id() + ) + ); + } + + public void stubSingleVariantMarket( + int httpStatus, + int marketId, + Identifiable variantId, + Locale language, + MarketDescriptions descriptions + ) { + stub( + httpStatus, + format( + "/v1/descriptions/%s/markets/%s/variants/%s.*", + language.getLanguage(), + marketId, + variantId.id() + ), + descriptions + ); + } + + private void stub(int httpStatus, String path) { + register(get(urlPathMatching(path)).willReturn(WireMock.status(httpStatus))); + } + + private void stub(int httpStatus, String path, Object descriptions) { + register( + get(urlPathMatching(path)) + .willReturn( + WireMock + .aResponse() + .withStatus(httpStatus) + .withBody(JaxbContexts.SportsApi.marshall(descriptions)) + ) + ); } - private void stub(MarketDescriptions descriptions) { - wireMockRule.stubFor( - get(urlPathMatching("/v1/descriptions/en/markets.xml.*")) - .willReturn(WireMock.ok(JaxbContexts.SportsApi.marshall(descriptions))) + private void stub(Object descriptions, String path) { + register( + get(urlPathMatching(path)).willReturn(WireMock.ok(JaxbContexts.SportsApi.marshall(descriptions))) ); } public void noMarketVariants() { - wireMockRule.stubFor( + register( get(urlPathEqualTo("/v1/descriptions/en/variants.xml")) .willReturn( WireMock.ok( @@ -80,4 +167,47 @@ public void noMarketVariants() { ) ); } + + public void returnNotFoundForCustomBetAvailableSelections() { + CapiResponse response = notFoundResponse( + "no available selections for a path " + "/v1/custombet/[^/]+/available_selections" + ); + register( + get(urlPathMatching("/v1/custombet/[^/]+/available_selections")) + .willReturn( + WireMock.aResponse().withBody(toXml(response)).withStatus(HttpStatus.SC_NOT_FOUND) + ) + ); + } + + public void returnNotFoundForCustomBetCalculate() { + CapiResponse response = notFoundResponse( + "no available selections for a path " + "/v1/custombet/calculate" + ); + register( + post(urlPathMatching("/v1/custombet/calculate")) + .willReturn( + WireMock.aResponse().withBody(toXml(response)).withStatus(HttpStatus.SC_NOT_FOUND) + ) + ); + } + + private static CapiResponse notFoundResponse(String message) { + CapiResponse capiNotFoundResponse = new CapiResponse(); + capiNotFoundResponse.setResponseCode("NOT_FOUND"); + capiNotFoundResponse.setMessage(message); + return capiNotFoundResponse; + } + + private void register(MappingBuilder mappingBuilder) { + stubRegistrar.accept(mappingBuilder); + } + + @SneakyThrows + private String toXml(Object content) { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + JAXB.marshal(content, bos); + return new String(bos.toByteArray(), Charsets.UTF_8); + } + } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/FlexScoreMarketIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/FlexScoreMarketIT.java new file mode 100644 index 00000000..6f3b1934 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/FlexScoreMarketIT.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Throw; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.CorrectScoreFlex.correctScoreFlexMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.correctScoreFlexMarket; +import static com.sportradar.unifiedodds.sdk.impl.Constants.*; +import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.Credentials.with; +import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.RabbitMqClientFactory.createRabbitMqClient; +import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.RabbitMqProducer.connectDeclaringExchange; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.http.client.Client; +import com.sportradar.unifiedodds.sdk.impl.Constants; +import com.sportradar.unifiedodds.sdk.impl.TimeUtilsImpl; +import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithOdds; +import com.sportradar.unifiedodds.sdk.oddsentities.OddsChange; +import com.sportradar.unifiedodds.sdk.oddsentities.OutcomeOdds; +import com.sportradar.unifiedodds.sdk.shared.FeedMessageBuilder; +import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.*; +import java.util.Locale; +import java.util.stream.Collectors; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import lombok.ToString; +import lombok.val; +import org.junit.*; +import org.junit.runner.RunWith; + +@RunWith(JUnitParamsRunner.class) +@Ignore +@SuppressWarnings( + { + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + "CyclomaticComplexity", + "ExecutableStatementCount", + "HiddenField", + "IllegalCatch", + "JavaNCSS", + "LineLength", + "MagicNumber", + "MethodLength", + "MultipleStringLiterals", + "OverloadMethodsDeclarationOrder", + "ParameterAssignment", + } +) +public class FlexScoreMarketIT { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); + + private final GlobalVariables globalVariables = new GlobalVariables(); + private final ApiSimulator apiSimulator = new ApiSimulator(wireMockRule); + + private final Credentials sdkCredentials = Credentials.with( + Constants.SDK_USERNAME, + Constants.SDK_PASSWORD + ); + private final VhostLocation vhostLocation = VhostLocation.at(RABBIT_BASE_URL, Constants.UF_VIRTUALHOST); + private final ExchangeLocation exchangeLocation = ExchangeLocation.at( + vhostLocation, + Constants.UF_EXCHANGE + ); + private final Credentials adminCredentials = Credentials.with( + Constants.ADMIN_USERNAME, + Constants.ADMIN_PASSWORD + ); + private final ConnectionFactory factory = new ConnectionFactory(); + private final MessagesInMemoryStorage messagesStorage = new MessagesInMemoryStorage(); + + private final WaiterForSingleMessage listinerWaitingFor = new WaiterForSingleMessage(messagesStorage); + private final Client rabbitMqClient = createRabbitMqClient( + RABBIT_IP, + with(ADMIN_USERNAME, ADMIN_PASSWORD), + Client::new + ); + private final RabbitMqUserSetup rabbitMqUserSetup = RabbitMqUserSetup.create( + VhostLocation.at(RABBIT_BASE_URL, UF_VIRTUALHOST), + rabbitMqClient + ); + + private BaseUrl sportsApiBaseUrl; + + public FlexScoreMarketIT() throws Exception {} + + @Before + public void setup() throws Exception { + rabbitMqUserSetup.setupUser(sdkCredentials); + sportsApiBaseUrl = BaseUrl.of("localhost", wireMockRule.port()); + } + + @After + public void tearDownProxy() { + rabbitMqUserSetup.revertChangesMade(); + } + + @Test + @Parameters(method = "scores") + public void scoreIsAdjustedToTheSpecifierFromTheFeedMessage(Score score) throws Exception { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(correctScoreFlexMarketDescription(aLanguage), aLanguage); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(Throw) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send( + messages.oddsChange(correctScoreFlexMarket(score.home, score.away)), + routingKeys.liveOddsChange() + ); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + + val collect = market + .getOutcomeOdds() + .stream() + .map(OutcomeOdds::getName) + .map(Score::new) + .collect(Collectors.toList()); + + assertThat(collect) + .allMatch(s -> s.home >= score.home, "home cannot be less than current home score"); + assertThat(collect) + .allMatch(s -> s.away >= score.away, "away cannot be less than current away score"); + } + } + + private MarketWithOdds theOnlyMarketIn( + OddsChange oddsChange + ) { + assertThat(oddsChange.getMarkets()).hasSize(1); + return oddsChange.getMarkets().get(0); + } + + private Score[] scores() { + return new Score[] { new Score(1, 3), new Score(14, 5), new Score(0, 0), new Score(4, 4) }; + } + + @ToString + private static class Score { + + private final int home; + private final int away; + + Score(int home, int away) { + this.home = home; + this.away = away; + } + + Score(String outcome) { + String[] homeAway = outcome.split(":"); + this.home = Integer.parseInt(homeAway[0]); + this.away = Integer.parseInt(homeAway[1]); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/GlobalVariables.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/GlobalVariables.java index b00123e2..13977ea1 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/GlobalVariables.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/GlobalVariables.java @@ -3,12 +3,15 @@ */ package com.sportradar.unifiedodds.sdk.conn; +import com.sportradar.utils.generic.testing.RandomInteger; import java.util.Optional; public class GlobalVariables { private Optional producer = Optional.empty(); - private Optional sportEvent = Optional.empty(); + private Optional sportEventUrn = Optional.empty(); + private Optional sportUrn = Optional.empty(); + private final int nodeId = 1; public ProducerId getProducer() { return producer.get(); @@ -18,11 +21,23 @@ public void setProducer(ProducerId producerId) { this.producer = Optional.of(producerId); } - public SportEvent getSportEvent() { - return sportEvent.get(); + public SportEvent getSportEventUrn() { + return sportEventUrn.get(); } - public void setSportEvent(SportEvent sportEvent) { - this.sportEvent = Optional.of(sportEvent); + public void setSportEventUrn(SportEvent sportEventUrn) { + this.sportEventUrn = Optional.of(sportEventUrn); + } + + public Sport getSportUrn() { + return sportUrn.get(); + } + + public void setSportUrn(Sport sportUrn) { + this.sportUrn = Optional.of(sportUrn); + } + + public int getNodeId() { + return nodeId; } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/Identifiable.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/Identifiable.java new file mode 100644 index 00000000..230a16a7 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/Identifiable.java @@ -0,0 +1,8 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn; + +public interface Identifiable { + String id(); +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketVariant.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketVariant.java new file mode 100644 index 00000000..49e8ca99 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketVariant.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn; + +import com.google.common.collect.ImmutableMap; +import com.sportradar.uf.sportsapi.datamodel.Mappings; +import com.sportradar.uf.sportsapi.datamodel.Mappings.Mapping.MappingOutcome; +import java.util.List; +import java.util.Map; +import lombok.Value; + +public interface MarketVariant extends Identifiable { + String id(); + + List outcomeIds(); + + default Map> mappings() { + return ImmutableMap.of(); + } + + @Value + @SuppressWarnings("VisibilityModifier") + class Mapping { + + int productId; + String productIds; + String sportId; + String marketId; + String sovTemplate; + + public Mappings.Mapping toSapiMapping(List mappingOutcome) { + Mappings.Mapping mapping = new Mappings.Mapping(); + mapping.setProductId(productId); + mapping.setProductIds(productIds); + mapping.setSportId(sportId); + mapping.setMarketId(marketId); + mapping.setSovTemplate(sovTemplate); + mapping.getMappingOutcome().addAll(mappingOutcome); + return mapping; + } + } + + @Value + @SuppressWarnings("VisibilityModifier") + class OutcomeMapping { + + String outcomeId; + String productOutcomeId; + String productOutcomeName; + + public Mappings.Mapping.MappingOutcome toSapiOutcomeMapping() { + MappingOutcome outcome = new MappingOutcome(); + outcome.setOutcomeId(outcomeId); + outcome.setProductOutcomeId(productOutcomeId); + outcome.setProductOutcomeName(productOutcomeName); + return outcome; + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithFaultyDescriptionIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithFaultyDescriptionIT.java index 84b091a8..41122f2e 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithFaultyDescriptionIT.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithFaultyDescriptionIT.java @@ -4,9 +4,14 @@ package com.sportradar.unifiedodds.sdk.conn; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static com.sportradar.unifiedodds.sdk.conn.OddEvenMarket.evenOutcomeOf; -import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.oddEvenDescription; -import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.oddEven; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.FreeTextMarketDescription.freeTextMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.NascarOutrights.*; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.notFoundWithEmptyMarket; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.NascarOutrightsOddEvenVariant.nascarEvenOutcomeOf; +import static com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds.evenOutcomeOf; import static com.sportradar.unifiedodds.sdk.impl.Constants.*; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_CATCH_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; @@ -15,8 +20,12 @@ import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.Credentials.with; import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.RabbitMqClientFactory.createRabbitMqClient; import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.RabbitMqProducer.connectDeclaringExchange; +import static com.sportradar.utils.Urns.SportEvents.getForAnyMatch; +import static com.sportradar.utils.domain.names.LanguageHolder.in; +import static com.sportradar.utils.domain.names.TranslationHolder.of; +import static java.util.Locale.*; +import static org.apache.http.HttpStatus.*; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.rabbitmq.client.ConnectionFactory; @@ -24,22 +33,21 @@ import com.sportradar.uf.sportsapi.datamodel.DescMarket; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; import com.sportradar.unifiedodds.sdk.exceptions.InitException; -import com.sportradar.unifiedodds.sdk.exceptions.NameGenerationException; import com.sportradar.unifiedodds.sdk.impl.Constants; import com.sportradar.unifiedodds.sdk.impl.TimeUtilsImpl; import com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy; -import com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketAssert; -import com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.OutcomeAssert; import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithOdds; import com.sportradar.unifiedodds.sdk.oddsentities.OddsChange; import com.sportradar.unifiedodds.sdk.shared.FeedMessageBuilder; import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.*; +import com.sportradar.utils.Urns; import java.io.IOException; import java.util.Locale; import java.util.concurrent.TimeoutException; import junitparams.JUnitParamsRunner; import junitparams.Parameters; import lombok.val; +import org.assertj.core.api.Assertions; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -69,26 +77,32 @@ public class MarketWithFaultyDescriptionIT { @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); - private GlobalVariables globalVariables = new GlobalVariables(); - private ApiSimulator apiSimulator = new ApiSimulator(wireMockRule); + private final GlobalVariables globalVariables = new GlobalVariables(); + private final ApiSimulator apiSimulator = new ApiSimulator(wireMockRule); - private Credentials sdkCredentials = Credentials.with(Constants.SDK_USERNAME, Constants.SDK_PASSWORD); - private VhostLocation vhostLocation = VhostLocation.at(RABBIT_BASE_URL, Constants.UF_VIRTUALHOST); - private ExchangeLocation exchangeLocation = ExchangeLocation.at(vhostLocation, Constants.UF_EXCHANGE); - private Credentials adminCredentials = Credentials.with( + private final Credentials sdkCredentials = Credentials.with( + Constants.SDK_USERNAME, + Constants.SDK_PASSWORD + ); + private final VhostLocation vhostLocation = VhostLocation.at(RABBIT_BASE_URL, Constants.UF_VIRTUALHOST); + private final ExchangeLocation exchangeLocation = ExchangeLocation.at( + vhostLocation, + Constants.UF_EXCHANGE + ); + private final Credentials adminCredentials = Credentials.with( Constants.ADMIN_USERNAME, Constants.ADMIN_PASSWORD ); - private ConnectionFactory factory = new ConnectionFactory(); - private MessagesInMemoryStorage messagesStorage = new MessagesInMemoryStorage(); + private final ConnectionFactory factory = new ConnectionFactory(); + private final MessagesInMemoryStorage messagesStorage = new MessagesInMemoryStorage(); - private WaiterForSingleMessage listinerWaitingFor = new WaiterForSingleMessage(messagesStorage); + private final WaiterForSingleMessage listinerWaitingFor = new WaiterForSingleMessage(messagesStorage); private final Client rabbitMqClient = createRabbitMqClient( RABBIT_IP, with(ADMIN_USERNAME, ADMIN_PASSWORD), Client::new ); - private RabbitMqUserSetup rabbitMqUserSetup = RabbitMqUserSetup.create( + private final RabbitMqUserSetup rabbitMqUserSetup = RabbitMqUserSetup.create( VhostLocation.at(RABBIT_BASE_URL, UF_VIRTUALHOST), rabbitMqClient ); @@ -104,7 +118,7 @@ public void setup() throws Exception { } @After - public void tearDownProxy() throws Exception { + public void tearDownProxy() { rabbitMqUserSetup.revertChangesMade(); } @@ -112,22 +126,18 @@ public void tearDownProxy() throws Exception { @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsWhenMarketDescriptionIsMissing( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) throws IOException, TimeoutException, InitException { globalVariables.setProducer(ProducerId.LIVE_ODDS); - FeedMessageBuilder messages = new FeedMessageBuilder(ProducerId.LIVE_ODDS, SportEvent.MATCH); - final int nodeId = 1; - Locale aLanguage = Locale.ENGLISH; - RoutingKeys routingKeys = new RoutingKeys( - ProducerId.LIVE_ODDS, - Sport.FOOTBALL, - SportEvent.MATCH, - nodeId - ); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); apiSimulator.defineBookmaker(); apiSimulator.activateOnlyLiveProducer(); - apiSimulator.stubEmptyMarketList(); + apiSimulator.stubEmptyMarketList(aLanguage); try ( val rabbitProducer = connectDeclaringExchange( @@ -137,19 +147,18 @@ public void marketNameRetrievalFailsWhenMarketDescriptionIsMissing( new TimeUtilsImpl() ); val sdk = SdkSetup - .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, nodeId) + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) .with(ListenerCollectingMessages.to(messagesStorage)) .with(exceptionHandlingStrategy) .withDefaultLanguage(aLanguage) .with1Session() .withOpenedFeed() ) { - rabbitProducer.send(messages.oddsChange(oddEven()), routingKeys.liveOddsChange()); + rabbitProducer.send(messages.oddsChange(oddEvenMarket()), routingKeys.liveOddsChange()); val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForDefaultLanguage(aLanguage, willRespectSdkStrategy); + assertThat(market).getNameForGiven(aLanguage, willFailRespectingSdkStrategy); } } @@ -160,19 +169,15 @@ public void outcomeNameRetrievalFailsWhenMarketDescriptionIsMissing( ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy ) throws IOException, TimeoutException, InitException { globalVariables.setProducer(ProducerId.LIVE_ODDS); - FeedMessageBuilder messages = new FeedMessageBuilder(ProducerId.LIVE_ODDS, SportEvent.MATCH); - final int nodeId = 1; - Locale aLanguage = Locale.ENGLISH; - RoutingKeys routingKeys = new RoutingKeys( - ProducerId.LIVE_ODDS, - Sport.FOOTBALL, - SportEvent.MATCH, - nodeId - ); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); apiSimulator.defineBookmaker(); apiSimulator.activateOnlyLiveProducer(); - apiSimulator.stubEmptyMarketList(); + apiSimulator.stubEmptyMarketList(aLanguage); try ( val rabbitProducer = connectDeclaringExchange( @@ -182,14 +187,14 @@ public void outcomeNameRetrievalFailsWhenMarketDescriptionIsMissing( new TimeUtilsImpl() ); val sdk = SdkSetup - .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, nodeId) + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) .with(ListenerCollectingMessages.to(messagesStorage)) .with(exceptionHandlingStrategy) .withDefaultLanguage(aLanguage) .with1Session() .withOpenedFeed() ) { - rabbitProducer.send(messages.oddsChange(oddEven()), routingKeys.liveOddsChange()); + rabbitProducer.send(messages.oddsChange(oddEvenMarket()), routingKeys.liveOddsChange()); val oddsChange = listinerWaitingFor.theOnlyOddsChange(); val outcome = evenOutcomeOf(theOnlyMarketIn(oddsChange)); @@ -206,19 +211,69 @@ public void outcomeNameRetrievalFailsWhenOutcomeDescriptionIsMissing( ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy ) throws IOException, TimeoutException, InitException { globalVariables.setProducer(ProducerId.LIVE_ODDS); - FeedMessageBuilder messages = new FeedMessageBuilder(ProducerId.LIVE_ODDS, SportEvent.MATCH); - final int nodeId = 1; - Locale aLanguage = Locale.ENGLISH; - RoutingKeys routingKeys = new RoutingKeys( - ProducerId.LIVE_ODDS, - Sport.FOOTBALL, - SportEvent.MATCH, - nodeId + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + val messageingSimulatorFactory = new MessagingSimulator.Factory( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl(), + globalVariables ); + Locale aLanguage = ENGLISH; + apiSimulator.defineBookmaker(); apiSimulator.activateOnlyLiveProducer(); - apiSimulator.stubMarketListContaining(oddEvenDescriptionMissingEvenOutcome()); + apiSimulator.stubMarketListContaining(oddEvenDescriptionMissingEvenOutcome(), aLanguage); + + try ( + val messagingSimulator = messageingSimulatorFactory.connectDeclaringExchange(); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + messagingSimulator.send(m -> m.oddsChange(oddEvenMarket()), RoutingKeys::liveOddsChange); + + val oddsChange = listinerWaitingFor.theOnlyOddsChange(); + val outcome = evenOutcomeOf(theOnlyMarketIn(oddsChange)); + + assertThat(outcome) + .nameIsNotBackedByOutcomeDescriptionForDefaultLanguage(aLanguage, willRespectSdkStrategy); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void singleVariantMarketApiFailuresAreThrottledPerLanguage( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale langA = ENGLISH; + Locale langB = FRENCH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), langA); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), langB); + apiSimulator.stubEmptyVariantList(langA); + apiSimulator.stubEmptyVariantList(langB); + apiSimulator.stubSingleVariantMarket( + SC_SERVICE_UNAVAILABLE, + FREE_TEXT_MARKET_ID, + nascarOutrightsOddEvenVariant(), + langA + ); + apiSimulator.stubSingleVariantMarket(nascarOutrightsOddEvenMarketDescription(langB), langB); try ( val rabbitProducer = connectDeclaringExchange( @@ -228,20 +283,226 @@ public void outcomeNameRetrievalFailsWhenOutcomeDescriptionIsMissing( new TimeUtilsImpl() ); val sdk = SdkSetup - .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, nodeId) + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) .with(ListenerCollectingMessages.to(messagesStorage)) .with(exceptionHandlingStrategy) - .withDefaultLanguage(aLanguage) + .withDefaultLanguage(langA) .with1Session() .withOpenedFeed() ) { - rabbitProducer.send(messages.oddsChange(oddEven()), routingKeys.liveOddsChange()); + rabbitProducer.send( + messages.oddsChange(nascarOutrightsOddEvenMarket()), + routingKeys.liveOddsChange() + ); val oddsChange = listinerWaitingFor.theOnlyOddsChange(); - val outcome = evenOutcomeOf(theOnlyMarketIn(oddsChange)); + MarketWithOdds market = theOnlyMarketIn(oddsChange); + val outcome = nascarEvenOutcomeOf(market); assertThat(outcome) - .nameIsNotBackedByOutcomeDescriptionForDefaultLanguage(aLanguage, willRespectSdkStrategy); + .nameIsNotBackedByOutcomeDescriptionForDefaultLanguage(langA, willRespectSdkStrategy); + assertThat(outcome) + .hasNameInNonDefaultLanguage(of(nascarEvenOutcomeDescription(langB).getName(), in(langB))); + + apiSimulator.stubSingleVariantMarket(nascarOutrightsOddEvenMarketDescription(langA), langA); + + assertThat(outcome) + .nameIsNotBackedByOutcomeDescriptionForDefaultLanguage(langA, willRespectSdkStrategy); + assertThat(outcome) + .hasNameInNonDefaultLanguage(of(nascarEvenOutcomeDescription(langB).getName(), in(langB))); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void singleVariantMarketDescriptionNotFoundResponsesForOneLanguageDoNotInterfereWithOtherLanguages( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale langA = ENGLISH; + Locale langB = FRENCH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), langA); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), langB); + apiSimulator.stubEmptyVariantList(langA); + apiSimulator.stubEmptyVariantList(langB); + apiSimulator.stubSingleVariantMarket( + SC_OK, + FREE_TEXT_MARKET_ID, + nascarOutrightsOddEvenVariant(), + langA, + notFoundWithEmptyMarket() + ); + apiSimulator.stubSingleVariantMarket(nascarOutrightsOddEvenMarketDescription(langB), langB); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(langA) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send( + messages.oddsChange(nascarOutrightsOddEvenMarket()), + routingKeys.liveOddsChange() + ); + + val oddsChange = listinerWaitingFor.theOnlyOddsChange(); + MarketWithOdds market = theOnlyMarketIn(oddsChange); + + Assertions.assertThat(market.getName(langA)).isEqualTo(freeTextMarketDescription().getName()); + + Assertions + .assertThat(market.getName(langB)) + .isEqualTo(nascarOutrightsOddEvenMarketDescription(langB).getName()); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void singleVariantMarketDescriptionNotFoundHttpStatusForOneLanguageDoNotInterfereWithOtherLanguages( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale langA = ENGLISH; + Locale langB = FRENCH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), langA); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), langB); + apiSimulator.stubEmptyVariantList(langA); + apiSimulator.stubEmptyVariantList(langB); + apiSimulator.stubSingleVariantMarket( + SC_NOT_FOUND, + FREE_TEXT_MARKET_ID, + nascarOutrightsOddEvenVariant(), + langA + ); + apiSimulator.stubSingleVariantMarket(nascarOutrightsOddEvenMarketDescription(langB), langB); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(langA) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send( + messages.oddsChange(nascarOutrightsOddEvenMarket()), + routingKeys.liveOddsChange() + ); + + val oddsChange = listinerWaitingFor.theOnlyOddsChange(); + MarketWithOdds market = theOnlyMarketIn(oddsChange); + + Assertions.assertThat(market.getName(langA)).isEqualTo(freeTextMarketDescription().getName()); + + Assertions + .assertThat(market.getName(langB)) + .isEqualTo(nascarOutrightsOddEvenMarketDescription(langB).getName()); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void singleVariantMarketApiFailuresAreThrottledPerLanguageForTwoSubsequentMessages( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale langA = ENGLISH; + Locale langB = FRENCH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), langA); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), langB); + apiSimulator.stubEmptyVariantList(langA); + apiSimulator.stubEmptyVariantList(langB); + apiSimulator.stubSingleVariantMarket( + SC_SERVICE_UNAVAILABLE, + FREE_TEXT_MARKET_ID, + nascarOutrightsOddEvenVariant(), + langA + ); + apiSimulator.stubSingleVariantMarket(nascarOutrightsOddEvenMarketDescription(langB), langB); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(langA) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send( + messages.oddsChange(nascarOutrightsOddEvenMarket()), + routingKeys.liveOddsChange() + ); + + val oddsChange = listinerWaitingFor.theOnlyOddsChange(); + MarketWithOdds market = theOnlyMarketIn(oddsChange); + val outcome = nascarEvenOutcomeOf(market); + + assertThat(outcome) + .nameIsNotBackedByOutcomeDescriptionForDefaultLanguage(langA, willRespectSdkStrategy); + assertThat(outcome) + .hasNameInNonDefaultLanguage(of(nascarEvenOutcomeDescription(langB).getName(), in(langB))); + + rabbitProducer.send( + messages.oddsChange(nascarOutrightsOddEvenMarket()), + routingKeys.liveOddsChange() + ); + + apiSimulator.stubSingleVariantMarket(nascarOutrightsOddEvenMarketDescription(langA), langA); + val oddsChange1 = listinerWaitingFor.secondOddsChange(); + MarketWithOdds market1 = theOnlyMarketIn(oddsChange1); + val outcome1 = nascarEvenOutcomeOf(market1); + + assertThat(outcome1) + .nameIsNotBackedByOutcomeDescriptionForDefaultLanguage(langA, willRespectSdkStrategy); + assertThat(outcome1) + .hasNameInNonDefaultLanguage(of(nascarEvenOutcomeDescription(langB).getName(), in(langB))); } } @@ -260,7 +521,7 @@ private MarketWithOdds theOnlyMarketIn( } private static DescMarket oddEvenDescriptionMissingEvenOutcome() { - DescMarket incompleteOddEvenDescription = oddEvenDescription(); + DescMarket incompleteOddEvenDescription = SapiMarketDescriptions.OddEven.oddEvenMarketDescription(); incompleteOddEvenDescription.getOutcomes().getOutcome().remove(1); return incompleteOddEvenDescription; } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithMissingNameIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithMissingNameIT.java new file mode 100644 index 00000000..c59684bd --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithMissingNameIT.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Throw; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.ExactGoals.exactGoalsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.FreeTextMarketDescription.freeTextMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.NascarOutrights.nascarOutrightsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OneXtwo.oneXtwoMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiVariantDescriptions.ExactGoals.fivePlusVariantDescription; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds.fivePlusVariant; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds.fivePlusVariant; +import static com.sportradar.unifiedodds.sdk.impl.Constants.*; +import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_CATCH_EXCEPTIONS; +import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; +import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.Credentials.with; +import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.RabbitMqClientFactory.createRabbitMqClient; +import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.RabbitMqProducer.connectDeclaringExchange; +import static org.assertj.core.api.Assertions.*; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.http.client.Client; +import com.sportradar.uf.sportsapi.datamodel.DescMarket; +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.exceptions.InitException; +import com.sportradar.unifiedodds.sdk.impl.Constants; +import com.sportradar.unifiedodds.sdk.impl.TimeUtilsImpl; +import com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy; +import com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketAssert; +import com.sportradar.unifiedodds.sdk.oddsentities.MarketDefinition; +import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithOdds; +import com.sportradar.unifiedodds.sdk.oddsentities.OddsChange; +import com.sportradar.unifiedodds.sdk.shared.FeedMessageBuilder; +import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.*; +import java.io.IOException; +import java.util.Locale; +import java.util.concurrent.TimeoutException; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.*; +import org.junit.runner.RunWith; + +@RunWith(JUnitParamsRunner.class) +@SuppressWarnings( + { + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + "CyclomaticComplexity", + "ExecutableStatementCount", + "HiddenField", + "IllegalCatch", + "JavaNCSS", + "LineLength", + "MagicNumber", + "MethodLength", + "MultipleStringLiterals", + "OverloadMethodsDeclarationOrder", + "ParameterAssignment", + } +) +public class MarketWithMissingNameIT { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); + + private final GlobalVariables globalVariables = new GlobalVariables(); + private final ApiSimulator apiSimulator = new ApiSimulator(wireMockRule); + + private final Credentials sdkCredentials = Credentials.with( + Constants.SDK_USERNAME, + Constants.SDK_PASSWORD + ); + private final VhostLocation vhostLocation = VhostLocation.at(RABBIT_BASE_URL, Constants.UF_VIRTUALHOST); + private final ExchangeLocation exchangeLocation = ExchangeLocation.at( + vhostLocation, + Constants.UF_EXCHANGE + ); + private final Credentials adminCredentials = Credentials.with( + Constants.ADMIN_USERNAME, + Constants.ADMIN_PASSWORD + ); + private final ConnectionFactory factory = new ConnectionFactory(); + private final MessagesInMemoryStorage messagesStorage = new MessagesInMemoryStorage(); + + private final WaiterForSingleMessage listinerWaitingFor = new WaiterForSingleMessage(messagesStorage); + private final Client rabbitMqClient = createRabbitMqClient( + RABBIT_IP, + with(ADMIN_USERNAME, ADMIN_PASSWORD), + Client::new + ); + private final RabbitMqUserSetup rabbitMqUserSetup = RabbitMqUserSetup.create( + VhostLocation.at(RABBIT_BASE_URL, UF_VIRTUALHOST), + rabbitMqClient + ); + + private BaseUrl sportsApiBaseUrl; + + public MarketWithMissingNameIT() throws Exception {} + + @Before + public void setup() throws Exception { + rabbitMqUserSetup.setupUser(sdkCredentials); + sportsApiBaseUrl = BaseUrl.of("localhost", wireMockRule.port()); + } + + @After + public void tearDownProxy() { + rabbitMqUserSetup.revertChangesMade(); + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void marketDescriptionHasNullName( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy unused + ) throws Exception { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + Locale aLanguage = Locale.ENGLISH; + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(nullifyName(oneXtwoMarketDescription()), aLanguage); + + try ( + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .withoutFeed() + ) { + val marketDescriptions = sdk.getMarketDescriptionManager().getMarketDescriptions(); + val namelessDescription = getOnlyElement(marketDescriptions); + + Assertions.assertThat(namelessDescription.getName(aLanguage)).isNull(); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void marketDefinitionWithNullNameIsHandedOverToCustomerCodeAndFailsUponNameRetrieval( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(nullifyName(oddEvenMarketDescription()), aLanguage); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send(messages.oddsChange(oddEvenMarket()), routingKeys.liveOddsChange()); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + MarketDefinition definition = market.getMarketDefinition(); + + assertThat(definition.getNameTemplate()).isNull(); + assertThat(definition.getNameTemplate(aLanguage)).isNull(); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void marketWithNullNameIsHandedOverToCustomerCodeAndFailsUponNameRetrieval( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(nullifyName(oddEvenMarketDescription()), aLanguage); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send(messages.oddsChange(oddEvenMarket()), routingKeys.liveOddsChange()); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + + MarketAssert.assertThat(market).getNameForDefault(aLanguage, willFailRespectingSdkStrategy); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void structuredVariantMarketWithNullNameIsHandedOverToCustomerCodeAndFailsUponNameRetrieval( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(nullifyName(exactGoalsMarketDescription()), aLanguage); + apiSimulator.stubVariantListContaining(fivePlusVariantDescription(), aLanguage); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send( + messages.oddsChange(exactGoalsMarket(fivePlusVariant())), + routingKeys.liveOddsChange() + ); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + + MarketAssert.assertThat(market).getNameForDefault(aLanguage, willFailRespectingSdkStrategy); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void unstructuredVariantMarketWithNullNameIsHandedOverToCustomerCodeAndFailsUponNameRetrieval( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), aLanguage); + apiSimulator.stubSingleVariantMarket(nullifyName(nascarOutrightsMarketDescription()), aLanguage); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send(messages.oddsChange(nascarOutrightsMarket()), routingKeys.liveOddsChange()); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + + MarketAssert.assertThat(market).getNameForDefault(aLanguage, willFailRespectingSdkStrategy); + } + } + + private Object[] exceptionHandlingStrategies() { + return new Object[][] { + { Throw, WILL_THROW_EXCEPTIONS }, + { ExceptionHandlingStrategy.Catch, WILL_CATCH_EXCEPTIONS }, + }; + } + + private MarketWithOdds theOnlyMarketIn( + OddsChange oddsChange + ) { + Assertions.assertThat(oddsChange.getMarkets().size()).isEqualTo(1); + return oddsChange.getMarkets().get(0); + } + + private static DescMarket nullifyName(DescMarket market) { + market.setName(null); + return market; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithMissingOutcomeNameIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithMissingOutcomeNameIT.java new file mode 100644 index 00000000..4be4b705 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MarketWithMissingOutcomeNameIT.java @@ -0,0 +1,541 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Throw; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.CorrectScoreFlex.correctScoreFlexMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.ExactGoals.exactGoalsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.FreeTextMarketDescription.freeTextMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.NascarOutrights.nascarOutrightsMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OneXtwo.oneXtwoMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiVariantDescriptions.ExactGoals.fivePlusVariantDescription; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds.fivePlusVariant; +import static com.sportradar.unifiedodds.sdk.impl.Constants.*; +import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_CATCH_EXCEPTIONS; +import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Prepositions.*; +import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.Credentials.with; +import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.RabbitMqClientFactory.createRabbitMqClient; +import static com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.RabbitMqProducer.connectDeclaringExchange; +import static java.util.stream.Collectors.toList; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.http.client.Client; +import com.sportradar.uf.sportsapi.datamodel.DescMarket; +import com.sportradar.uf.sportsapi.datamodel.DescVariant; +import com.sportradar.uf.sportsapi.datamodel.Mappings; +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.conn.marketids.FlexScoreMarketIds; +import com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds; +import com.sportradar.unifiedodds.sdk.entities.markets.MarketDescription; +import com.sportradar.unifiedodds.sdk.entities.markets.MarketMappingData; +import com.sportradar.unifiedodds.sdk.entities.markets.OutcomeDescription; +import com.sportradar.unifiedodds.sdk.entities.markets.OutcomeMappingData; +import com.sportradar.unifiedodds.sdk.exceptions.InitException; +import com.sportradar.unifiedodds.sdk.impl.Constants; +import com.sportradar.unifiedodds.sdk.impl.TimeUtilsImpl; +import com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy; +import com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.OutcomeAssert; +import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithOdds; +import com.sportradar.unifiedodds.sdk.oddsentities.OddsChange; +import com.sportradar.unifiedodds.sdk.oddsentities.OutcomeDefinition; +import com.sportradar.unifiedodds.sdk.oddsentities.OutcomeOdds; +import com.sportradar.unifiedodds.sdk.shared.FeedMessageBuilder; +import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.*; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; +import java.util.function.Predicate; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.*; +import org.junit.runner.RunWith; + +@RunWith(JUnitParamsRunner.class) +@Ignore +@SuppressWarnings( + { + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + "CyclomaticComplexity", + "ExecutableStatementCount", + "HiddenField", + "IllegalCatch", + "JavaNCSS", + "LineLength", + "MagicNumber", + "MethodLength", + "MultipleStringLiterals", + "OverloadMethodsDeclarationOrder", + "ParameterAssignment", + } +) +public class MarketWithMissingOutcomeNameIT { + + @Rule + public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); + + private final GlobalVariables globalVariables = new GlobalVariables(); + private final ApiSimulator apiSimulator = new ApiSimulator(wireMockRule); + + private final Credentials sdkCredentials = Credentials.with( + Constants.SDK_USERNAME, + Constants.SDK_PASSWORD + ); + private final VhostLocation vhostLocation = VhostLocation.at(RABBIT_BASE_URL, Constants.UF_VIRTUALHOST); + private final ExchangeLocation exchangeLocation = ExchangeLocation.at( + vhostLocation, + Constants.UF_EXCHANGE + ); + private final Credentials adminCredentials = Credentials.with( + Constants.ADMIN_USERNAME, + Constants.ADMIN_PASSWORD + ); + private final ConnectionFactory factory = new ConnectionFactory(); + private final MessagesInMemoryStorage messagesStorage = new MessagesInMemoryStorage(); + + private final WaiterForSingleMessage listinerWaitingFor = new WaiterForSingleMessage(messagesStorage); + private final Client rabbitMqClient = createRabbitMqClient( + RABBIT_IP, + with(ADMIN_USERNAME, ADMIN_PASSWORD), + Client::new + ); + private final RabbitMqUserSetup rabbitMqUserSetup = RabbitMqUserSetup.create( + VhostLocation.at(RABBIT_BASE_URL, UF_VIRTUALHOST), + rabbitMqClient + ); + + private BaseUrl sportsApiBaseUrl; + + public MarketWithMissingOutcomeNameIT() throws Exception {} + + @Before + public void setup() throws Exception { + rabbitMqUserSetup.setupUser(sdkCredentials); + sportsApiBaseUrl = BaseUrl.of("localhost", wireMockRule.port()); + } + + @After + public void tearDownProxy() { + rabbitMqUserSetup.revertChangesMade(); + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void marketOutcomeHasEmptyName( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy unused + ) throws Exception { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + Locale aLanguage = Locale.ENGLISH; + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(nullifyFirstOutcomeName(oneXtwoMarketDescription()), aLanguage); + + try ( + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .withoutFeed() + ) { + val marketDescriptions = sdk.getMarketDescriptionManager().getMarketDescriptions(); + val description = getOnlyElement(marketDescriptions); + val faultyOutcome = firstOutcomeOf(description); + + Assertions.assertThat(faultyOutcome.getName(aLanguage)).isEmpty(); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void marketWithNullOutcomeNameIsHandedOverToCustomerCodeAndFailsUponNameRetrieval( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(nullifyFirstOutcomeName(oddEvenMarketDescription()), aLanguage); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send(messages.oddsChange(oddEvenMarket()), routingKeys.liveOddsChange()); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + val faultyOutcome = firstOutcomeOf(market); + + OutcomeAssert.assertThat(faultyOutcome).getNameForGiven(aLanguage, willFailRespectingSdkStrategy); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void marketWithNullOutcomeNameTemplateIsHandedOverToCustomerCodeAsNull( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy notImportant + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + val outcomeId = OddEvenMarketIds.anyOddEvenOutcomeId(); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining( + nullifyOutcomeName(oddEvenMarketDescription(), outcomeId), + aLanguage + ); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send(messages.oddsChange(oddEvenMarket()), routingKeys.liveOddsChange()); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + val nameTemplate = getOutcomeDefinition(market, outcomeId).getNameTemplate(); + + Assertions.assertThat(nameTemplate).isEmpty(); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void invariantFlexScoreMarketWithNullOutcomeNameIsHandedOverToCustomerCodeAndFailsUponNameRetrieval( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining( + nullifyFirstOutcomeName(correctScoreFlexMarketDescription(aLanguage)), + aLanguage + ); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send(messages.oddsChange(correctScoreFlexMarket()), routingKeys.liveOddsChange()); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + val faultyOutcome = firstOutcomeOf(market); + + OutcomeAssert.assertThat(faultyOutcome).getNameForGiven(aLanguage, willFailRespectingSdkStrategy); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void invariantFlexScoreFutsalMarketWithNullMappingOutcomeNameIsHandedOverToCustomerCodeAndReturnsNull( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy notImportant + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining( + nullifyMappingOutcomeNameForZeroToZeroOutcome(correctScoreFlexMarketDescription(aLanguage)), + aLanguage + ); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send(messages.oddsChange(correctScoreFlexMarket()), routingKeys.liveOddsChange()); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + + val validMapping = getValidMapping(market, aLanguage); + val producerOutcomeNamesForZeroToZeroOutcome = getProducerOutcomeNameForZeroToZeroOutcome( + validMapping, + aLanguage + ); + + Assertions.assertThat(producerOutcomeNamesForZeroToZeroOutcome).isNull(); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void structuredVariantMarketWithNullNameIsHandedOverToCustomerCodeAndFailsUponNameRetrieval( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(exactGoalsMarketDescription(), aLanguage); + apiSimulator.stubVariantListContaining( + nullifyFirstOutcomeName(fivePlusVariantDescription()), + aLanguage + ); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send( + messages.oddsChange(exactGoalsMarket(fivePlusVariant())), + routingKeys.liveOddsChange() + ); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + val faultyOutcome = firstOutcomeOf(market); + + OutcomeAssert + .assertThat(faultyOutcome) + .getNameForDefault(aLanguage, willFailRespectingSdkStrategy); + } + } + + @Test + @Parameters(method = "exceptionHandlingStrategies") + public void unstructuredVariantMarketWithNullNameIsHandedOverToCustomerCodeAndFailsUponNameRetrieval( + ExceptionHandlingStrategy exceptionHandlingStrategy, + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy + ) throws IOException, TimeoutException, InitException { + globalVariables.setProducer(ProducerId.LIVE_ODDS); + globalVariables.setSportEventUrn(SportEvent.MATCH); + globalVariables.setSportUrn(Sport.FOOTBALL); + FeedMessageBuilder messages = new FeedMessageBuilder(globalVariables); + Locale aLanguage = Locale.ENGLISH; + RoutingKeys routingKeys = new RoutingKeys(globalVariables); + + apiSimulator.defineBookmaker(); + apiSimulator.activateOnlyLiveProducer(); + apiSimulator.stubMarketListContaining(freeTextMarketDescription(), aLanguage); + apiSimulator.stubSingleVariantMarket( + nullifyFirstOutcomeName(nascarOutrightsMarketDescription()), + aLanguage + ); + + try ( + val rabbitProducer = connectDeclaringExchange( + exchangeLocation, + adminCredentials, + factory, + new TimeUtilsImpl() + ); + val sdk = SdkSetup + .with(sdkCredentials, RABBIT_BASE_URL, sportsApiBaseUrl, globalVariables.getNodeId()) + .with(ListenerCollectingMessages.to(messagesStorage)) + .with(exceptionHandlingStrategy) + .withDefaultLanguage(aLanguage) + .with1Session() + .withOpenedFeed() + ) { + rabbitProducer.send(messages.oddsChange(nascarOutrightsMarket()), routingKeys.liveOddsChange()); + + val market = theOnlyMarketIn(listinerWaitingFor.theOnlyOddsChange()); + val faultyOutcome = firstOutcomeOf(market); + + OutcomeAssert + .assertThat(faultyOutcome) + .getNameForDefault(aLanguage, willFailRespectingSdkStrategy); + } + } + + private Object[] exceptionHandlingStrategies() { + return new Object[][] { + { Throw, WILL_THROW_EXCEPTIONS }, + { ExceptionHandlingStrategy.Catch, WILL_CATCH_EXCEPTIONS }, + }; + } + + private MarketWithOdds theOnlyMarketIn( + OddsChange oddsChange + ) { + Assertions.assertThat(oddsChange.getMarkets()).hasSize(1); + return oddsChange.getMarkets().get(0); + } + + private static DescMarket nullifyFirstOutcomeName(DescMarket market) { + market.getOutcomes().getOutcome().get(0).setName(null); + return market; + } + + private static DescMarket nullifyOutcomeName(DescMarket market, String outcomeId) { + val outcome = market + .getOutcomes() + .getOutcome() + .stream() + .filter(o -> o.getId().equals(outcomeId)) + .findFirst() + .get(); + outcome.setName(null); + return market; + } + + private static DescVariant nullifyFirstOutcomeName(DescVariant variant) { + variant.getOutcomes().getOutcome().get(0).setName(null); + return variant; + } + + private DescMarket nullifyMappingOutcomeNameForZeroToZeroOutcome(DescMarket market) { + val onlyOneMappingElement = getOnlyElement(market.getMappings().getMapping()); + val mappingOutcome = findOutcomeMapping( + in(onlyOneMappingElement), + by(FlexScoreMarketIds.ZERO_TO_ZERO_OUTCOME_ID) + ); + mappingOutcome.setProductOutcomeName(null); + return market; + } + + private static Mappings.Mapping.MappingOutcome findOutcomeMapping( + Mappings.Mapping onlyOneMappingElement, + String outcomeId + ) { + return onlyOneMappingElement + .getMappingOutcome() + .stream() + .filter(mo -> mo.getOutcomeId().equals(outcomeId)) + .findFirst() + .get(); + } + + private static OutcomeOdds firstOutcomeOf(MarketWithOdds market) { + return market.getOutcomeOdds().get(0); + } + + private OutcomeDescription firstOutcomeOf(MarketDescription market) { + return market.getOutcomes().get(0); + } + + private static String getProducerOutcomeNameForZeroToZeroOutcome( + MarketMappingData validMappings, + Locale aLanguage + ) { + List producerOutcomeNames = validMappings + .getOutcomeMappings() + .entrySet() + .stream() + .filter(isZeroToZeroOutcome()) + .map(getProducerOutcomeName(aLanguage)) + .collect(toList()); + return getOnlyElement(producerOutcomeNames); + } + + private static Function, String> getProducerOutcomeName( + Locale aLanguage + ) { + return e -> e.getValue().getProducerOutcomeName(aLanguage); + } + + private static Predicate> isZeroToZeroOutcome() { + return om -> om.getValue().getOutcomeId().equals(FlexScoreMarketIds.ZERO_TO_ZERO_OUTCOME_ID); + } + + private static MarketMappingData getValidMapping(MarketWithOdds market, Locale aLanguage) { + return getOnlyElement(market.getMarketDefinition().getValidMappings(aLanguage, true)); + } + + private static OutcomeDefinition getOutcomeDefinition(MarketWithOdds market, String anyOutcomeId) { + val outcome = market + .getOutcomeOdds() + .stream() + .filter(o -> o.getId().equals(anyOutcomeId)) + .findFirst() + .get(); + return outcome.getOutcomeDefinition(); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MessagingSimulator.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MessagingSimulator.java new file mode 100644 index 00000000..9a6f76a1 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/MessagingSimulator.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn; + +import com.rabbitmq.client.ConnectionFactory; +import com.sportradar.unifiedodds.sdk.impl.TimeUtils; +import com.sportradar.unifiedodds.sdk.shared.FeedMessageBuilder; +import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.Credentials; +import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.ExchangeLocation; +import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.RabbitMqProducer; +import java.io.IOException; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; + +public class MessagingSimulator implements AutoCloseable { + + private final RabbitMqProducer rabbitMqProducer; + private final FeedMessageBuilder messages; + private final RoutingKeys routingKeys; + + public MessagingSimulator( + final ExchangeLocation exchangeLocation, + final Credentials credentials, + final ConnectionFactory connectionFactory, + final TimeUtils time, + final GlobalVariables globalVariables + ) throws IOException, TimeoutException { + rabbitMqProducer = + RabbitMqProducer.connectDeclaringExchange(exchangeLocation, credentials, connectionFactory, time); + messages = new FeedMessageBuilder(globalVariables); + routingKeys = new RoutingKeys(globalVariables); + } + + @Override + public void close() throws IOException, TimeoutException { + rabbitMqProducer.close(); + } + + public void send( + Function chooseMessage, + Function chooseRoutingKey + ) { + rabbitMqProducer.send(chooseMessage.apply(messages), chooseRoutingKey.apply(routingKeys)); + } + + public static class Factory { + + private final ExchangeLocation exchangeLocation; + private final Credentials credentials; + private final ConnectionFactory connectionFactory; + private final TimeUtils time; + private final GlobalVariables globalVariables; + + public Factory( + final ExchangeLocation exchangeLocation, + final Credentials credentials, + final ConnectionFactory connectionFactory, + final TimeUtils time, + GlobalVariables globalVariables + ) { + this.exchangeLocation = exchangeLocation; + this.credentials = credentials; + this.connectionFactory = connectionFactory; + this.time = time; + this.globalVariables = globalVariables; + } + + public MessagingSimulator connectDeclaringExchange() throws IOException, TimeoutException { + return new MessagingSimulator( + exchangeLocation, + credentials, + connectionFactory, + time, + globalVariables + ); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/RabbitProducer.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/RabbitProducer.java index ebd351aa..715da47b 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/RabbitProducer.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/RabbitProducer.java @@ -75,7 +75,8 @@ public RabbitProducer(TestProducersProvider producersProvider, final RabbitMqUse ProducersAlive = new HashMap<>(); GlobalVariables variables = new GlobalVariables(); variables.setProducer(ProducerId.LIVE_ODDS); - feedMessageBuilder = new FeedMessageBuilder(ProducerId.LIVE_ODDS, SportEvent.ANY); + variables.setSportEventUrn(SportEvent.MATCH); + feedMessageBuilder = new FeedMessageBuilder(variables); isRunning = false; try { diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/RoutingKeys.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/RoutingKeys.java index b8959986..e4995fd1 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/RoutingKeys.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/RoutingKeys.java @@ -7,25 +7,19 @@ public class RoutingKeys { - private final ProducerId producerId; - private final Sport sport; - private final SportEvent sportEvent; - private final int nodeId; + private final GlobalVariables globalVariables; - public RoutingKeys(ProducerId producerId, Sport sport, SportEvent sportEvent, int nodeId) { - this.producerId = producerId; - this.sport = sport; - this.sportEvent = sportEvent; - this.nodeId = nodeId; + public RoutingKeys(GlobalVariables globalVariables) { + this.globalVariables = globalVariables; } public String liveOddsChange() { return format( "hi.-.live.odds_change.%d.sr:%s.%d.%s", - sport.getUrn().getId(), - sportEvent.getUrn().getType(), - sportEvent.getUrn().getId(), - nodeId + globalVariables.getSportUrn().getUrn().getId(), + globalVariables.getSportEventUrn().getUrn().getType(), + globalVariables.getSportEventUrn().getUrn().getId(), + globalVariables.getNodeId() ); } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiBookmakerDetails.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiBookmakerDetails.java new file mode 100644 index 00000000..aa46e552 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiBookmakerDetails.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn; + +import com.sportradar.uf.sportsapi.datamodel.BookmakerDetails; +import com.sportradar.uf.sportsapi.datamodel.ResponseCode; +import com.sportradar.unifiedodds.sdk.testutil.jaxb.XmlGregorianCalendars; +import lombok.SneakyThrows; + +public class SapiBookmakerDetails { + + @SneakyThrows + public static BookmakerDetails valid() { + final BookmakerDetails bookmakerDetails = new BookmakerDetails(); + int anyBookmakerId = 1; + bookmakerDetails.setBookmakerId(anyBookmakerId); + bookmakerDetails.setExpireAt(XmlGregorianCalendars.anyFutureDate()); + bookmakerDetails.setVirtualHost("/unifiedfeed/" + anyBookmakerId); + bookmakerDetails.setResponseCode(ResponseCode.OK); + return bookmakerDetails; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiMarketDescriptions.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiMarketDescriptions.java index 784ad240..9c7c8ce4 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiMarketDescriptions.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiMarketDescriptions.java @@ -3,85 +3,1542 @@ */ package com.sportradar.unifiedodds.sdk.conn; -import static com.sportradar.unifiedodds.sdk.conn.OddEvenMarket.EVEN_OUTCOME_ID; -import static com.sportradar.unifiedodds.sdk.conn.OddEvenMarket.ODD_OUTCOME_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ChampionshipFreeTextMarketIds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds.EXACT_GOALS_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FlexScoreMarketIds.CORRECT_SCORE_FLEX_SCORE_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.FREE_TEXT_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.NascarOutrightsOddEvenVariant.NASCAR_EVEN_OUTCOME_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.NascarOutrightsOddEvenVariant.NASCAR_ODD_OUTCOME_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.nascarOutrightsVariant; +import static com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.OneXtwoMarketIds.*; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Prepositions.*; import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toMap; +import static org.assertj.core.api.Assertions.assertThat; -import com.sportradar.uf.sportsapi.datamodel.DescMarket; -import com.sportradar.uf.sportsapi.datamodel.DescOutcomes; -import java.util.Arrays; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.sportradar.uf.sportsapi.datamodel.*; +import com.sportradar.unifiedodds.sdk.conn.MarketVariant.OutcomeMapping; +import com.sportradar.unifiedodds.sdk.conn.marketids.FlexScoreMarketIds; +import com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds; +import com.sportradar.unifiedodds.sdk.impl.UnifiedFeedConstants; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.val; +@SuppressWarnings("ClassDataAbstractionCoupling") public final class SapiMarketDescriptions { - private SapiMarketDescriptions() {} + public static final String LANGUAGE_NOT_SUPPORTED_BY_TEST_FIXTURE = + "Language not supported by test fixture"; - public static DescMarket oddEvenDescription() { - return oddEvenDescription(Locale.ENGLISH); + public static DescOutcomes.Outcome getOutcomeDescription(String outcomeId, DescMarket marketDescription) { + val outcomes = marketDescription + .getOutcomes() + .getOutcome() + .stream() + .filter(o -> outcomeId.equals(o.getId())) + .collect(Collectors.toList()); + assertThat(outcomes.size()).isOne(); + return outcomes.get(0); } - public static DescMarket oddEvenDescription(Locale language) { - return oddEvenDescription(MarketTranslation.getFor(language)); + public static DescMarket removeAllOutcomesFrom(DescMarket marketDescription) { + marketDescription.getOutcomes().getOutcome().clear(); + return marketDescription; } - public static DescMarket oddEvenDescription(MarketTranslation translation) { - DescMarket market = new DescMarket(); - market.setId(OddEvenMarket.ID); - market.setName(translation.marketName); - market.setOutcomes(new DescOutcomes()); - market.getOutcomes().getOutcome().add(oddOutcomeDescription()); - market.getOutcomes().getOutcome().add(evenOutcomeDescription()); - return market; + public static MarketDescriptions notFoundWithEmptyMarket() { + DescMarket emptyMarketWithIdZero = new DescMarket(); + MarketDescriptions responseForPreMatchWithFaultyMarketAndNotFoundStatus = new MarketDescriptions(); + responseForPreMatchWithFaultyMarketAndNotFoundStatus.setResponseCode(ResponseCode.NOT_FOUND); + responseForPreMatchWithFaultyMarketAndNotFoundStatus.getMarket().add(emptyMarketWithIdZero); + return responseForPreMatchWithFaultyMarketAndNotFoundStatus; } - public static DescOutcomes.Outcome oddOutcomeDescription() { - return oddOutcomeDescription(Locale.ENGLISH); + public static class ExactGoals { + + public static DescMarket exactGoalsMarketDescription(Locale language) { + return exactGoalsMarketDescription(MarketTranslation.getFor(language)); + } + + public static DescMarket exactGoalsMarketDescription() { + return exactGoalsMarketDescription(MarketTranslation.EN); + } + + private static DescMarket exactGoalsMarketDescription(MarketTranslation translation) { + DescMarket market = new DescMarket(); + market.setId(EXACT_GOALS_MARKET_ID); + market.setName(translation.marketName); + market.setGroups("all|score|regular_play"); + market.setSpecifiers(SpecifierDescriptions.variant()); + return market; + } + + @Getter + @RequiredArgsConstructor + private enum MarketTranslation { + EN(Locale.ENGLISH, "Exact goal"), + FR(Locale.FRENCH, "Nombre de buts exact"); + + private final Locale language; + private final String marketName; + + public static ExactGoals.MarketTranslation getFor(Locale language) { + return stream(ExactGoals.MarketTranslation.values()) + .filter(translation -> translation.language.equals(language)) + .findFirst() + .orElseThrow(() -> new RuntimeException(LANGUAGE_NOT_SUPPORTED_BY_TEST_FIXTURE)); + } + } } - public static DescOutcomes.Outcome oddOutcomeDescription(Locale language) { - return oddOutcomeDescription(MarketTranslation.getFor(language)); + public static class ChampionshipFreeTextMarketDescription { + + public static DescMarket championshipFreeTextMarketDescription() { + DescMarket market = new DescMarket(); + market.setId(CHAMPIONSHIP_FREE_TEXT_MARKET_ID); + market.setName("Championship free text market"); + market.setOutcomeType(UnifiedFeedConstants.FREETEXT_VARIANT_VALUE); + market.setIncludesOutcomesOfType(UnifiedFeedConstants.OUTCOMETEXT_VARIANT_VALUE); + market.setGroups("all"); + market.setSpecifiers(SpecifierDescriptions.variant()); + return market; + } + } + + public static class ChampionshipFreeTextOpenMarketDescription { + + public static DescMarket championshipFreeTextMarketDescription() { + DescMarket market = new DescMarket(); + market.setId(CHAMPIONSHIP_FREE_TEXT_OPEN_MARKET_ID); + market.setName("Championship free text market"); + market.setOutcomeType(UnifiedFeedConstants.FREETEXT_VARIANT_VALUE); + market.setIncludesOutcomesOfType(UnifiedFeedConstants.OUTCOMETEXT_VARIANT_VALUE); + market.setGroups("all"); + market.setSpecifiers(SpecifierDescriptions.variantAndVersion()); + return market; + } } - public static DescOutcomes.Outcome oddOutcomeDescription(MarketTranslation translation) { - DescOutcomes.Outcome evenOutcome = new DescOutcomes.Outcome(); - evenOutcome.setId(ODD_OUTCOME_ID); - evenOutcome.setName(translation.getOddOutcomeName()); - return evenOutcome; + public static class FreeTextMarketDescription { + + public static DescMarket freeTextMarketDescription() { + DescMarket market = new DescMarket(); + market.setId(FREE_TEXT_MARKET_ID); + market.setName("Free text market"); + market.setOutcomeType(UnifiedFeedConstants.FREETEXT_VARIANT_VALUE); + market.setIncludesOutcomesOfType(UnifiedFeedConstants.OUTCOMETEXT_VARIANT_VALUE); + market.setGroups("all"); + market.setSpecifiers(SpecifierDescriptions.variant()); + return market; + } } - public static DescOutcomes.Outcome evenOutcomeDescription() { - return evenOutcomeDescription(Locale.ENGLISH); + public static class NascarOutrights { + + private static final Map WINNER_ENGLISH_TRANSLATIONS = ImmutableMap + .builder() + .put("pre:outcometext:9832848", "Gilliland, Todd") + .put("pre:outcometext:11154385", "Smith, Zane") + .put("pre:outcometext:8191791", "Preece, Ryan") + .put("pre:outcometext:9840952", "Haley, Justin") + .put("pre:outcometext:9513940", "Gragson, Noah") + .put("pre:outcometext:6258421", "Blaney, Ryan") + .put("pre:outcometext:7084505", "Buescher, Chris") + .put("pre:outcometext:6569371", "Wallace Jr., Darrell") + .put("pre:outcometext:505632", "Hamlin, Denny") + .put("pre:outcometext:6621072", "Elliott, Chase") + .put("pre:outcometext:1014491", "Keselowski, Brad") + .put("pre:outcometext:318760", "Busch, Kyle") + .put("pre:outcometext:7125706", "Reddick, Tyler") + .put("pre:outcometext:5520588", "Dillon, Austin") + .put("pre:outcometext:7458692", "Lajoie, Corey") + .put("pre:outcometext:12572116", "Hocevar, Carson") + .put("pre:outcometext:16714708", "Gibbs, Ty") + .put("pre:outcometext:1277357", "Logano, Joey") + .put("pre:outcometext:1238461", "McDowell, Michael") + .put("pre:outcometext:7696944", "Suarez, Daniel") + .put("pre:outcometext:11787001", "Kraus, Derek") + .put("pre:outcometext:11160813", "Berry, Josh") + .put("pre:outcometext:5695824", "Stenhouse Jr, Ricky") + .put("pre:outcometext:6593846", "Larson, Kyle") + .put("pre:outcometext:6944491", "Bowman, Alex") + .put("pre:outcometext:6864419", "Chastain, Ross") + .put("pre:outcometext:8217403", "Bell, Christopher") + .put("pre:outcometext:8820457", "Byron, William") + .put("pre:outcometext:7401353", "Nemechek, John Hunter") + .put("pre:outcometext:9065140", "Cindric, Austin") + .put("pre:outcometext:9110228", "Hemric, Daniel") + .put("pre:outcometext:7125704", "Jones, Erik") + .put("pre:outcometext:9832847", "Briscoe, Chase") + .put("pre:outcometext:9861894", "Burton, Harrison") + .put("pre:outcometext:9840951", "Grala, Kaz") + .put("pre:outcometext:318761", "Truex Jr, Martin") + .build(); + + private static final Map ODD_EVEN_ENGLISH_TRANSLATIONS = ImmutableMap + .builder() + .put(NASCAR_EVEN_OUTCOME_ID, "Even") + .put(NASCAR_ODD_OUTCOME_ID, "Odd") + .build(); + + public static DescOutcomes.Outcome nascarEvenOutcomeDescription() { + return nascarEvenOutcomeDescription(Locale.ENGLISH); + } + + public static DescOutcomes.Outcome nascarEvenOutcomeDescription(Locale language) { + return nascarEvenOutcomeDescription(OddEvenMarketTranslation.getFor(language)); + } + + private static DescOutcomes.Outcome nascarEvenOutcomeDescription( + OddEvenMarketTranslation translation + ) { + DescOutcomes.Outcome evenOutcome = new DescOutcomes.Outcome(); + evenOutcome.setId(NASCAR_EVEN_OUTCOME_ID); + evenOutcome.setName(translation.outcomeTranslations.get(NASCAR_EVEN_OUTCOME_ID)); + return evenOutcome; + } + + public static DescMarket nascarOutrightsOddEvenMarketDescription(Locale language) { + return nascarOutrightsOddEvenMarketDescription(OddEvenMarketTranslation.getFor(language)); + } + + public static DescMarket nascarOutrightsOddEvenMarketDescription() { + return nascarOutrightsOddEvenMarketDescription(OddEvenMarketTranslation.EN); + } + + private static DescMarket nascarOutrightsOddEvenMarketDescription( + OddEvenMarketTranslation oddEvenMarketTranslation + ) { + DescMarket market = new DescMarket(); + market.setId(FREE_TEXT_MARKET_ID); + market.setName(oddEvenMarketTranslation.getMarketName()); + market.setVariant(nascarOutrightsOddEvenVariant().id()); + populateOutcomes( + from(FreeTextMarketIds.nascarOutrightsOddEvenVariant()), + to(market), + with(oddEvenMarketTranslation) + ); + return market; + } + + public static DescMarket nascarOutrightsMarketDescription(Locale language) { + return nascarOutrightsMarketDescription(WinnerMarketTranslation.getFor(language)); + } + + public static DescMarket nascarOutrightsMarketDescription() { + return nascarOutrightsMarketDescription(WinnerMarketTranslation.EN); + } + + private static DescMarket nascarOutrightsMarketDescription( + WinnerMarketTranslation winnerMarketTranslation + ) { + DescMarket market = new DescMarket(); + market.setId(FREE_TEXT_MARKET_ID); + market.setName(winnerMarketTranslation.getMarketName()); + market.setVariant(nascarOutrightsVariant().id()); + populateOutcomes( + from(FreeTextMarketIds.nascarOutrightsVariant()), + to(market), + in(winnerMarketTranslation) + ); + return market; + } + + public static DescOutcomes.Outcome outcomeDescriptionFor(String outcomeId) { + return outcomeDescriptionFor(outcomeId, Locale.ENGLISH); + } + + public static DescOutcomes.Outcome outcomeDescriptionFor(String outcomeId, Locale language) { + return outcomeDescriptionFor(outcomeId, WinnerMarketTranslation.getFor(language)); + } + + private static DescOutcomes.Outcome outcomeDescriptionFor( + String outcomeId, + MarketTranslation translation + ) { + return outcome(outcomeId, translation.getOutcomeTranslations().get(outcomeId)); + } + + private static void populateOutcomes( + MarketVariant fromVariant, + DescMarket toMarket, + MarketTranslation translation + ) { + val outcomes = new DescOutcomes(); + populateOutcomes(fromVariant, to(outcomes), with(translation)); + toMarket.setOutcomes(outcomes); + } + + private static void populateOutcomes( + MarketVariant fromVariant, + DescOutcomes toOutcomes, + MarketTranslation translation + ) { + fromVariant + .outcomeIds() + .forEach(id -> + toOutcomes.getOutcome().add(outcome(id, translation.getOutcomeTranslations().get(id))) + ); + } + + private static DescOutcomes.Outcome outcome(String id, String name) { + val outcome = new DescOutcomes.Outcome(); + outcome.setId(id); + outcome.setName(name); + return outcome; + } + + public interface MarketTranslation { + Locale getLanguage(); + String getMarketName(); + Map getOutcomeTranslations(); + } + + @RequiredArgsConstructor + @Getter + private enum WinnerMarketTranslation implements MarketTranslation { + EN( + Locale.ENGLISH, + "NASCAR Cup Series - Shriners Children’s 500 - Winner", + WINNER_ENGLISH_TRANSLATIONS + ), + FR_SAME_AS_ENGLISH_IN_PROD( + Locale.FRENCH, + "NASCAR Cup Series - Shriners Children’s 500 - Winner", + WINNER_ENGLISH_TRANSLATIONS + ); + + private final Locale language; + private final String marketName; + private final Map outcomeTranslations; + + public static WinnerMarketTranslation getFor(Locale language) { + return stream(WinnerMarketTranslation.values()) + .filter(translation -> translation.language.equals(language)) + .findFirst() + .orElseThrow(() -> new RuntimeException(LANGUAGE_NOT_SUPPORTED_BY_TEST_FIXTURE)); + } + } + + @RequiredArgsConstructor + @Getter + private enum OddEvenMarketTranslation implements MarketTranslation { + EN( + Locale.ENGLISH, + "NASCAR Cup Series - Shriners Children’s 500 - Car Number of Race Winner", + ODD_EVEN_ENGLISH_TRANSLATIONS + ), + FR_SAME_AS_ENGLISH_IN_PROD( + Locale.FRENCH, + "NASCAR Cup Series - Shriners Children’s 500 - Car Number of Race Winner", + ODD_EVEN_ENGLISH_TRANSLATIONS + ); + + private final Locale language; + private final String marketName; + private final Map outcomeTranslations; + + public static OddEvenMarketTranslation getFor(Locale language) { + return stream(OddEvenMarketTranslation.values()) + .filter(translation -> translation.language.equals(language)) + .findFirst() + .orElseThrow(() -> new RuntimeException(LANGUAGE_NOT_SUPPORTED_BY_TEST_FIXTURE)); + } + } + } + + public static class NflAfcConferenceOutrights { + + private static final Map ENGLISH_TRANSLATIONS = ImmutableMap + .builder() + .put("pre:outcometext:35260", "Jacksonville Jaguars") + .put("pre:outcometext:33133", "Pittsburgh Steelers") + .put("pre:outcometext:35270", "New England Patriots") + .put("pre:outcometext:35278", "Cleveland Browns") + .put("pre:outcometext:119349", "Houston Texans") + .put("pre:outcometext:33135", "Cincinnati Bengals") + .put("pre:outcometext:33136", "Las Vegas Raiders") + .put("pre:outcometext:35274", "Los Angeles Chargers") + .put("pre:outcometext:35266", "Kansas City Chiefs") + .put("pre:outcometext:35254", "Indianapolis Colts") + .put("pre:outcometext:35250", "Baltimore Ravens") + .put("pre:outcometext:33138", "Miami Dolphins") + .put("pre:outcometext:35264", "Buffalo Bills") + .put("pre:outcometext:35249", "Tennessee Titans") + .put("pre:outcometext:35253", "Denver Broncos") + .put("pre:outcometext:35263", "New York Jets") + .build(); + + public static DescMarket nflAfcConferenceOutrightsMarketDescription(Locale language) { + return nflAfcConferenceOutrightsMarketDescription(WinnerMarketTranslation.getFor(language)); + } + + public static DescMarket nflAfcConferenceOutrightsMarketDescription() { + return nflAfcConferenceOutrightsMarketDescription(WinnerMarketTranslation.EN); + } + + private static DescMarket nflAfcConferenceOutrightsMarketDescription( + WinnerMarketTranslation marketTranslation + ) { + DescMarket market = new DescMarket(); + market.setId(CHAMPIONSHIP_FREE_TEXT_MARKET_ID); + market.setName(marketTranslation.getMarketName()); + market.setVariant(nflAfcConferenceOutrightsVariant().id()); + populateOutcomes(from(nflAfcConferenceOutrightsVariant()), to(market), with(marketTranslation)); + return market; + } + + public static DescMarket openMarket(DescMarket market) { + if (market.getId() != CHAMPIONSHIP_FREE_TEXT_MARKET_ID) { + throw new RuntimeException("Only championship free text market is supported"); + } + market.setId(CHAMPIONSHIP_FREE_TEXT_OPEN_MARKET_ID); + return market; + } + + private static void populateOutcomes( + MarketVariant fromVariant, + DescMarket toMarket, + MarketTranslation translation + ) { + val outcomes = new DescOutcomes(); + populateOutcomes(fromVariant, to(outcomes), with(translation)); + toMarket.setOutcomes(outcomes); + } + + private static void populateOutcomes( + MarketVariant fromVariant, + DescOutcomes toOutcomes, + MarketTranslation translation + ) { + fromVariant + .outcomeIds() + .forEach(id -> + toOutcomes.getOutcome().add(outcome(id, translation.getOutcomeTranslations().get(id))) + ); + } + + private static DescOutcomes.Outcome outcome(String id, String name) { + val outcome = new DescOutcomes.Outcome(); + outcome.setId(id); + outcome.setName(name); + return outcome; + } + + public interface MarketTranslation { + Locale getLanguage(); + String getMarketName(); + Map getOutcomeTranslations(); + } + + @RequiredArgsConstructor + @Getter + private enum WinnerMarketTranslation implements MarketTranslation { + EN(Locale.ENGLISH, "NFL 2017/18 AFC Conference - Winner", ENGLISH_TRANSLATIONS), + FR_SAME_AS_ENGLISH_IN_PROD( + Locale.FRENCH, + "NFL 2017/18 AFC Conference - Winner", + ENGLISH_TRANSLATIONS + ); + + private final Locale language; + private final String marketName; + private final Map outcomeTranslations; + + public static WinnerMarketTranslation getFor(Locale language) { + return stream(WinnerMarketTranslation.values()) + .filter(translation -> translation.language.equals(language)) + .findFirst() + .orElseThrow(() -> new RuntimeException(LANGUAGE_NOT_SUPPORTED_BY_TEST_FIXTURE)); + } + } } - public static DescOutcomes.Outcome evenOutcomeDescription(Locale language) { - return evenOutcomeDescription(MarketTranslation.getFor(language)); + public static class OddEven { + + public static DescMarket oddEvenMarketDescription() { + return oddEvenMarketDescription(Locale.ENGLISH); + } + + public static DescMarket oddEvenMarketDescription(Locale language) { + return oddEvenMarketDescription(MarketTranslation.getFor(language)); + } + + private static DescMarket oddEvenMarketDescription(MarketTranslation translation) { + DescMarket market = new DescMarket(); + market.setId(ODD_EVEN_MARKET_ID); + market.setName(translation.marketName); + market.setOutcomes(new DescOutcomes()); + market.getOutcomes().getOutcome().add(oddOutcomeDescription()); + market.getOutcomes().getOutcome().add(evenOutcomeDescription()); + return market; + } + + public static DescOutcomes.Outcome oddOutcomeDescription() { + return oddOutcomeDescription(Locale.ENGLISH); + } + + public static DescOutcomes.Outcome oddOutcomeDescription(Locale language) { + return oddOutcomeDescription(MarketTranslation.getFor(language)); + } + + private static DescOutcomes.Outcome oddOutcomeDescription(MarketTranslation translation) { + DescOutcomes.Outcome evenOutcome = new DescOutcomes.Outcome(); + evenOutcome.setId(ODD_OUTCOME_ID); + evenOutcome.setName(translation.getOddOutcomeName()); + return evenOutcome; + } + + public static DescOutcomes.Outcome evenOutcomeDescription() { + return evenOutcomeDescription(Locale.ENGLISH); + } + + public static DescOutcomes.Outcome evenOutcomeDescription(Locale language) { + return evenOutcomeDescription(MarketTranslation.getFor(language)); + } + + private static DescOutcomes.Outcome evenOutcomeDescription(MarketTranslation translation) { + DescOutcomes.Outcome evenOutcome = new DescOutcomes.Outcome(); + evenOutcome.setId(EVEN_OUTCOME_ID); + evenOutcome.setName(translation.getEvenOutcomeName()); + return evenOutcome; + } + + @RequiredArgsConstructor + @Getter + private enum MarketTranslation { + EN(Locale.ENGLISH, "Odd/Even", "Odd", "Even"), + FR(Locale.FRENCH, "Pair/Impair", "impair", "pair"); + + private final Locale language; + private final String marketName; + private final String oddOutcomeName; + private final String evenOutcomeName; + + public static MarketTranslation getFor(Locale language) { + return stream(MarketTranslation.values()) + .filter(translation -> translation.language.equals(language)) + .findFirst() + .orElseThrow(() -> new RuntimeException(LANGUAGE_NOT_SUPPORTED_BY_TEST_FIXTURE)); + } + } } - public static DescOutcomes.Outcome evenOutcomeDescription(MarketTranslation translation) { - DescOutcomes.Outcome evenOutcome = new DescOutcomes.Outcome(); - evenOutcome.setId(EVEN_OUTCOME_ID); - evenOutcome.setName(translation.getEvenOutcomeName()); - return evenOutcome; + public static class OneXtwo { + + public static DescMarket oneXtwoMarketDescription() { + return oneXtwoMarketDescription(Locale.ENGLISH); + } + + public static DescMarket oneXtwoMarketDescription(Locale language) { + return oneXtwoMarketDescription(MarketTranslation.getFor(language)); + } + + private static DescMarket oneXtwoMarketDescription(MarketTranslation translation) { + DescMarket market = new DescMarket(); + market.setId(ONE_X_TWO_MARKET_ID); + market.setName(translation.marketName); + market.setGroups("all|score|regular_play"); + market.setOutcomes(new DescOutcomes()); + market.getOutcomes().getOutcome().add(competitor1outcomeDescription()); + market.getOutcomes().getOutcome().add(competitor2outcomeDescription()); + return market; + } + + public static DescOutcomes.Outcome competitor1outcomeDescription() { + return competitor1outcomeDescription(Locale.ENGLISH); + } + + public static DescOutcomes.Outcome competitor1outcomeDescription(Locale language) { + return competitor1outcomeDescription(MarketTranslation.getFor(language)); + } + + private static DescOutcomes.Outcome competitor1outcomeDescription(MarketTranslation translation) { + DescOutcomes.Outcome evenOutcome = new DescOutcomes.Outcome(); + evenOutcome.setId(COMPETITOR_1_OUTCOME_ID); + evenOutcome.setName(translation.getCompetitor1OutcomeName()); + return evenOutcome; + } + + public static DescOutcomes.Outcome drawOutcomeDescription() { + return drawOutcomeDescription(Locale.ENGLISH); + } + + public static DescOutcomes.Outcome drawOutcomeDescription(Locale language) { + return drawOutcomeDescription(MarketTranslation.getFor(language)); + } + + private static DescOutcomes.Outcome drawOutcomeDescription(MarketTranslation translation) { + DescOutcomes.Outcome evenOutcome = new DescOutcomes.Outcome(); + evenOutcome.setId(DRAW_OUTCOME_ID); + evenOutcome.setName(translation.getDrawOutcomeName()); + return evenOutcome; + } + + public static DescOutcomes.Outcome competitor2outcomeDescription() { + return competitor2outcomeDescription(Locale.ENGLISH); + } + + public static DescOutcomes.Outcome competitor2outcomeDescription(Locale language) { + return competitor2outcomeDescription(MarketTranslation.getFor(language)); + } + + private static DescOutcomes.Outcome competitor2outcomeDescription(MarketTranslation translation) { + DescOutcomes.Outcome evenOutcome = new DescOutcomes.Outcome(); + evenOutcome.setId(COMPETITOR_2_OUTCOME_ID); + evenOutcome.setName(translation.getCompetitor2OutcomeName()); + return evenOutcome; + } + + @RequiredArgsConstructor + @Getter + private enum MarketTranslation { + EN(Locale.ENGLISH, "1x2", "{$competitor1}", "Match nul", "{$competitor2}"); + + private final Locale language; + private final String marketName; + private final String competitor1OutcomeName; + private final String drawOutcomeName; + private final String competitor2OutcomeName; + + public static MarketTranslation getFor(Locale language) { + return stream(MarketTranslation.values()) + .filter(translation -> translation.language.equals(language)) + .findFirst() + .orElseThrow(() -> new RuntimeException(LANGUAGE_NOT_SUPPORTED_BY_TEST_FIXTURE)); + } + } } - @RequiredArgsConstructor - @Getter - private static enum MarketTranslation { - EN(Locale.ENGLISH, "Odd/Even", "Odd", "Even"), - FR(Locale.FRENCH, "Pair/Impair", "impair", "pair"); - - private final Locale language; - private final String marketName; - private final String oddOutcomeName; - private final String evenOutcomeName; - - public static MarketTranslation getFor(Locale language) { - return stream(MarketTranslation.values()) - .filter(translation -> translation.language.equals(language)) - .findFirst() - .orElseThrow(() -> new RuntimeException("Language not supported by test fixture")); + public static class CorrectScoreFlex { + + // + private static final Map ENGLISH_TRANSLATIONS = ImmutableMap + .builder() + .put("1058", "0:0") + .put("1059", "0:1") + .put("1060", "0:2") + .put("1061", "0:3") + .put("1062", "0:4") + .put("1063", "0:5") + .put("1064", "0:6") + .put("1065", "0:7") + .put("1066", "0:8") + .put("1067", "0:9") + .put("1068", "0:10") + .put("1069", "0:11") + .put("1070", "0:12") + .put("1071", "0:13") + .put("1072", "0:14") + .put("1073", "0:15") + .put("1074", "0:16") + .put("1075", "0:17") + .put("1076", "0:18") + .put("1077", "0:19") + .put("1078", "1:0") + .put("1079", "1:1") + .put("1080", "1:2") + .put("1081", "1:3") + .put("1082", "1:4") + .put("1083", "1:5") + .put("1084", "1:6") + .put("1085", "1:7") + .put("1086", "1:8") + .put("1087", "1:9") + .put("1088", "1:10") + .put("1089", "1:11") + .put("1090", "1:12") + .put("1091", "1:13") + .put("1092", "1:14") + .put("1093", "1:15") + .put("1094", "1:16") + .put("1095", "1:17") + .put("1096", "1:18") + .put("1097", "1:19") + .put("1098", "2:0") + .put("1099", "2:1") + .put("1100", "2:2") + .put("1101", "2:3") + .put("1102", "2:4") + .put("1103", "2:5") + .put("1104", "2:6") + .put("1105", "2:7") + .put("1106", "2:8") + .put("1107", "2:9") + .put("1108", "2:10") + .put("1109", "2:11") + .put("1110", "2:12") + .put("1111", "2:13") + .put("1112", "2:14") + .put("1113", "2:15") + .put("1114", "2:16") + .put("1115", "2:17") + .put("1116", "2:18") + .put("1117", "2:19") + .put("1118", "3:0") + .put("1119", "3:1") + .put("1120", "3:2") + .put("1121", "3:3") + .put("1122", "3:4") + .put("1123", "3:5") + .put("1124", "3:6") + .put("1125", "3:7") + .put("1126", "3:8") + .put("1127", "3:9") + .put("1128", "3:10") + .put("1129", "3:11") + .put("1130", "3:12") + .put("1131", "3:13") + .put("1132", "3:14") + .put("1133", "3:15") + .put("1134", "3:16") + .put("1135", "3:17") + .put("1136", "3:18") + .put("1137", "3:19") + .put("1138", "4:0") + .put("1139", "4:1") + .put("1140", "4:2") + .put("1141", "4:3") + .put("1142", "4:4") + .put("1143", "4:5") + .put("1144", "4:6") + .put("1145", "4:7") + .put("1146", "4:8") + .put("1147", "4:9") + .put("1148", "4:10") + .put("1149", "4:11") + .put("1150", "4:12") + .put("1151", "4:13") + .put("1152", "4:14") + .put("1153", "4:15") + .put("1154", "4:16") + .put("1155", "4:17") + .put("1156", "4:18") + .put("1157", "4:19") + .put("1158", "5:0") + .put("1159", "5:1") + .put("1160", "5:2") + .put("1161", "5:3") + .put("1162", "5:4") + .put("1163", "5:5") + .put("1164", "5:6") + .put("1165", "5:7") + .put("1166", "5:8") + .put("1167", "5:9") + .put("1168", "5:10") + .put("1169", "5:11") + .put("1170", "5:12") + .put("1171", "5:13") + .put("1172", "5:14") + .put("1173", "5:15") + .put("1174", "5:16") + .put("1175", "5:17") + .put("1176", "5:18") + .put("1177", "5:19") + .put("1178", "6:0") + .put("1179", "6:1") + .put("1180", "6:2") + .put("1181", "6:3") + .put("1182", "6:4") + .put("1183", "6:5") + .put("1184", "6:6") + .put("1185", "6:7") + .put("1186", "6:8") + .put("1187", "6:9") + .put("1188", "6:10") + .put("1189", "6:11") + .put("1190", "6:12") + .put("1191", "6:13") + .put("1192", "6:14") + .put("1193", "6:15") + .put("1194", "6:16") + .put("1195", "6:17") + .put("1196", "6:18") + .put("1197", "6:19") + .put("1198", "7:0") + .put("1199", "7:1") + .put("1200", "7:2") + .put("1201", "7:3") + .put("1202", "7:4") + .put("1203", "7:5") + .put("1204", "7:6") + .put("1205", "7:7") + .put("1206", "7:8") + .put("1207", "7:9") + .put("1208", "7:10") + .put("1209", "7:11") + .put("1210", "7:12") + .put("1211", "7:13") + .put("1212", "7:14") + .put("1213", "7:15") + .put("1214", "7:16") + .put("1215", "7:17") + .put("1216", "7:18") + .put("1217", "7:19") + .put("1218", "8:0") + .put("1219", "8:1") + .put("1220", "8:2") + .put("1221", "8:3") + .put("1222", "8:4") + .put("1223", "8:5") + .put("1224", "8:6") + .put("1225", "8:7") + .put("1226", "8:8") + .put("1227", "8:9") + .put("1228", "8:10") + .put("1229", "8:11") + .put("1230", "8:12") + .put("1231", "8:13") + .put("1232", "8:14") + .put("1233", "8:15") + .put("1234", "8:16") + .put("1235", "8:17") + .put("1236", "8:18") + .put("1237", "8:19") + .put("1238", "9:0") + .put("1239", "9:1") + .put("1240", "9:2") + .put("1241", "9:3") + .put("1242", "9:4") + .put("1243", "9:5") + .put("1244", "9:6") + .put("1245", "9:7") + .put("1246", "9:8") + .put("1247", "9:9") + .put("1248", "9:10") + .put("1249", "9:11") + .put("1250", "9:12") + .put("1251", "9:13") + .put("1252", "9:14") + .put("1253", "9:15") + .put("1254", "9:16") + .put("1255", "9:17") + .put("1256", "9:18") + .put("1257", "9:19") + .put("1258", "10:0") + .put("1259", "10:1") + .put("1260", "10:2") + .put("1261", "10:3") + .put("1262", "10:4") + .put("1263", "10:5") + .put("1264", "10:6") + .put("1265", "10:7") + .put("1266", "10:8") + .put("1267", "10:9") + .put("1268", "10:10") + .put("1269", "10:11") + .put("1270", "10:12") + .put("1271", "10:13") + .put("1272", "10:14") + .put("1273", "10:15") + .put("1274", "10:16") + .put("1275", "10:17") + .put("1276", "10:18") + .put("1277", "10:19") + .put("1278", "11:0") + .put("1279", "11:1") + .put("1280", "11:2") + .put("1281", "11:3") + .put("1282", "11:4") + .put("1283", "11:5") + .put("1284", "11:6") + .put("1285", "11:7") + .put("1286", "11:8") + .put("1287", "11:9") + .put("1288", "11:10") + .put("1289", "11:11") + .put("1290", "11:12") + .put("1291", "11:13") + .put("1292", "11:14") + .put("1293", "11:15") + .put("1294", "11:16") + .put("1295", "11:17") + .put("1296", "11:18") + .put("1297", "11:19") + .put("1298", "12:0") + .put("1299", "12:1") + .put("1300", "12:2") + .put("1301", "12:3") + .put("1302", "12:4") + .put("1303", "12:5") + .put("1304", "12:6") + .put("1305", "12:7") + .put("1306", "12:8") + .put("1307", "12:9") + .put("1308", "12:10") + .put("1309", "12:11") + .put("1310", "12:12") + .put("1311", "12:13") + .put("1312", "12:14") + .put("1313", "12:15") + .put("1314", "12:16") + .put("1315", "12:17") + .put("1316", "12:18") + .put("1317", "12:19") + .put("1318", "13:0") + .put("1319", "13:1") + .put("1320", "13:2") + .put("1321", "13:3") + .put("1322", "13:4") + .put("1323", "13:5") + .put("1324", "13:6") + .put("1325", "13:7") + .put("1326", "13:8") + .put("1327", "13:9") + .put("1328", "13:10") + .put("1329", "13:11") + .put("1330", "13:12") + .put("1331", "13:13") + .put("1332", "13:14") + .put("1333", "13:15") + .put("1334", "13:16") + .put("1335", "13:17") + .put("1336", "13:18") + .put("1337", "13:19") + .put("1338", "14:0") + .put("1339", "14:1") + .put("1340", "14:2") + .put("1341", "14:3") + .put("1342", "14:4") + .put("1343", "14:5") + .put("1344", "14:6") + .put("1345", "14:7") + .put("1346", "14:8") + .put("1347", "14:9") + .put("1348", "14:10") + .put("1349", "14:11") + .put("1350", "14:12") + .put("1351", "14:13") + .put("1352", "14:14") + .put("1353", "14:15") + .put("1354", "14:16") + .put("1355", "14:17") + .put("1356", "14:18") + .put("1357", "14:19") + .put("1358", "15:0") + .put("1359", "15:1") + .put("1360", "15:2") + .put("1361", "15:3") + .put("1362", "15:4") + .put("1363", "15:5") + .put("1364", "15:6") + .put("1365", "15:7") + .put("1366", "15:8") + .put("1367", "15:9") + .put("1368", "15:10") + .put("1369", "15:11") + .put("1370", "15:12") + .put("1371", "15:13") + .put("1372", "15:14") + .put("1373", "15:15") + .put("1374", "15:16") + .put("1375", "15:17") + .put("1376", "15:18") + .put("1377", "15:19") + .put("1378", "16:0") + .put("1379", "16:1") + .put("1380", "16:2") + .put("1381", "16:3") + .put("1382", "16:4") + .put("1383", "16:5") + .put("1384", "16:6") + .put("1385", "16:7") + .put("1386", "16:8") + .put("1387", "16:9") + .put("1388", "16:10") + .put("1389", "16:11") + .put("1390", "16:12") + .put("1391", "16:13") + .put("1392", "16:14") + .put("1393", "16:15") + .put("1394", "16:16") + .put("1395", "16:17") + .put("1396", "16:18") + .put("1397", "16:19") + .put("1398", "17:0") + .put("1399", "17:1") + .put("1400", "17:2") + .put("1401", "17:3") + .put("1402", "17:4") + .put("1403", "17:5") + .put("1404", "17:6") + .put("1405", "17:7") + .put("1406", "17:8") + .put("1407", "17:9") + .put("1408", "17:10") + .put("1409", "17:11") + .put("1410", "17:12") + .put("1411", "17:13") + .put("1412", "17:14") + .put("1413", "17:15") + .put("1414", "17:16") + .put("1415", "17:17") + .put("1416", "17:18") + .put("1417", "17:19") + .put("1418", "18:0") + .put("1419", "18:1") + .put("1420", "18:2") + .put("1421", "18:3") + .put("1422", "18:4") + .put("1423", "18:5") + .put("1424", "18:6") + .put("1425", "18:7") + .put("1426", "18:8") + .put("1427", "18:9") + .put("1428", "18:10") + .put("1429", "18:11") + .put("1430", "18:12") + .put("1431", "18:13") + .put("1432", "18:14") + .put("1433", "18:15") + .put("1434", "18:16") + .put("1435", "18:17") + .put("1436", "18:18") + .put("1437", "18:19") + .put("1438", "19:0") + .put("1439", "19:1") + .put("1440", "19:2") + .put("1441", "19:3") + .put("1442", "19:4") + .put("1443", "19:5") + .put("1444", "19:6") + .put("1445", "19:7") + .put("1446", "19:8") + .put("1447", "19:9") + .put("1448", "19:10") + .put("1449", "19:11") + .put("1450", "19:12") + .put("1451", "19:13") + .put("1452", "19:14") + .put("1453", "19:15") + .put("1454", "19:16") + .put("1455", "19:17") + .put("1456", "19:18") + .put("1457", "19:19") + .build(); + + // + + // + private static final Map OUTCOME_MAPPING_ENGLISH_TRANSLATIONS = ImmutableList + .builder() + .add(new OutcomeMapping("1058", "700", "0:0")) + .add(new OutcomeMapping("1059", "702", "0:1")) + .add(new OutcomeMapping("1060", "704", "0:2")) + .add(new OutcomeMapping("1061", "706", "0:3")) + .add(new OutcomeMapping("1062", "708", "0:4")) + .add(new OutcomeMapping("1063", "710", "0:5")) + .add(new OutcomeMapping("1064", "712", "0:6")) + .add(new OutcomeMapping("1065", "714", "0:7")) + .add(new OutcomeMapping("1066", "716", "0:8")) + .add(new OutcomeMapping("1067", "718", "0:9")) + .add(new OutcomeMapping("1068", "720", "0:10")) + .add(new OutcomeMapping("1069", "722", "0:11")) + .add(new OutcomeMapping("1070", "724", "0:12")) + .add(new OutcomeMapping("1071", "726", "0:13")) + .add(new OutcomeMapping("1072", "728", "0:14")) + .add(new OutcomeMapping("1073", "730", "0:15")) + .add(new OutcomeMapping("1074", "732", "0:16")) + .add(new OutcomeMapping("1075", "734", "0:17")) + .add(new OutcomeMapping("1076", "736", "0:18")) + .add(new OutcomeMapping("1077", "738", "0:19")) + .add(new OutcomeMapping("1078", "740", "1:0")) + .add(new OutcomeMapping("1079", "742", "1:1")) + .add(new OutcomeMapping("1080", "744", "1:2")) + .add(new OutcomeMapping("1081", "746", "1:3")) + .add(new OutcomeMapping("1082", "748", "1:4")) + .add(new OutcomeMapping("1083", "750", "1:5")) + .add(new OutcomeMapping("1084", "752", "1:6")) + .add(new OutcomeMapping("1085", "754", "1:7")) + .add(new OutcomeMapping("1086", "756", "1:8")) + .add(new OutcomeMapping("1087", "758", "1:9")) + .add(new OutcomeMapping("1088", "760", "1:10")) + .add(new OutcomeMapping("1089", "762", "1:11")) + .add(new OutcomeMapping("1090", "764", "1:12")) + .add(new OutcomeMapping("1091", "766", "1:13")) + .add(new OutcomeMapping("1092", "768", "1:14")) + .add(new OutcomeMapping("1093", "770", "1:15")) + .add(new OutcomeMapping("1094", "772", "1:16")) + .add(new OutcomeMapping("1095", "774", "1:17")) + .add(new OutcomeMapping("1096", "776", "1:18")) + .add(new OutcomeMapping("1097", "778", "1:19")) + .add(new OutcomeMapping("1098", "780", "2:0")) + .add(new OutcomeMapping("1099", "782", "2:1")) + .add(new OutcomeMapping("1100", "784", "2:2")) + .add(new OutcomeMapping("1101", "786", "2:3")) + .add(new OutcomeMapping("1102", "788", "2:4")) + .add(new OutcomeMapping("1103", "790", "2:5")) + .add(new OutcomeMapping("1104", "792", "2:6")) + .add(new OutcomeMapping("1105", "794", "2:7")) + .add(new OutcomeMapping("1106", "796", "2:8")) + .add(new OutcomeMapping("1107", "798", "2:9")) + .add(new OutcomeMapping("1108", "800", "2:10")) + .add(new OutcomeMapping("1109", "802", "2:11")) + .add(new OutcomeMapping("1110", "804", "2:12")) + .add(new OutcomeMapping("1111", "806", "2:13")) + .add(new OutcomeMapping("1112", "808", "2:14")) + .add(new OutcomeMapping("1113", "810", "2:15")) + .add(new OutcomeMapping("1114", "812", "2:16")) + .add(new OutcomeMapping("1115", "814", "2:17")) + .add(new OutcomeMapping("1116", "816", "2:18")) + .add(new OutcomeMapping("1117", "818", "2:19")) + .add(new OutcomeMapping("1118", "820", "3:0")) + .add(new OutcomeMapping("1119", "822", "3:1")) + .add(new OutcomeMapping("1120", "824", "3:2")) + .add(new OutcomeMapping("1121", "826", "3:3")) + .add(new OutcomeMapping("1122", "828", "3:4")) + .add(new OutcomeMapping("1123", "830", "3:5")) + .add(new OutcomeMapping("1124", "832", "3:6")) + .add(new OutcomeMapping("1125", "834", "3:7")) + .add(new OutcomeMapping("1126", "836", "3:8")) + .add(new OutcomeMapping("1127", "838", "3:9")) + .add(new OutcomeMapping("1128", "840", "3:10")) + .add(new OutcomeMapping("1129", "842", "3:11")) + .add(new OutcomeMapping("1130", "844", "3:12")) + .add(new OutcomeMapping("1131", "846", "3:13")) + .add(new OutcomeMapping("1132", "848", "3:14")) + .add(new OutcomeMapping("1133", "850", "3:15")) + .add(new OutcomeMapping("1134", "852", "3:16")) + .add(new OutcomeMapping("1135", "854", "3:17")) + .add(new OutcomeMapping("1136", "856", "3:18")) + .add(new OutcomeMapping("1137", "858", "3:19")) + .add(new OutcomeMapping("1138", "860", "4:0")) + .add(new OutcomeMapping("1139", "862", "4:1")) + .add(new OutcomeMapping("1140", "864", "4:2")) + .add(new OutcomeMapping("1141", "866", "4:3")) + .add(new OutcomeMapping("1142", "868", "4:4")) + .add(new OutcomeMapping("1143", "870", "4:5")) + .add(new OutcomeMapping("1144", "872", "4:6")) + .add(new OutcomeMapping("1145", "874", "4:7")) + .add(new OutcomeMapping("1146", "876", "4:8")) + .add(new OutcomeMapping("1147", "878", "4:9")) + .add(new OutcomeMapping("1148", "880", "4:10")) + .add(new OutcomeMapping("1149", "882", "4:11")) + .add(new OutcomeMapping("1150", "884", "4:12")) + .add(new OutcomeMapping("1151", "886", "4:13")) + .add(new OutcomeMapping("1152", "888", "4:14")) + .add(new OutcomeMapping("1153", "890", "4:15")) + .add(new OutcomeMapping("1154", "892", "4:16")) + .add(new OutcomeMapping("1155", "894", "4:17")) + .add(new OutcomeMapping("1156", "896", "4:18")) + .add(new OutcomeMapping("1157", "898", "4:19")) + .add(new OutcomeMapping("1158", "900", "5:0")) + .add(new OutcomeMapping("1159", "902", "5:1")) + .add(new OutcomeMapping("1160", "904", "5:2")) + .add(new OutcomeMapping("1161", "906", "5:3")) + .add(new OutcomeMapping("1162", "908", "5:4")) + .add(new OutcomeMapping("1163", "910", "5:5")) + .add(new OutcomeMapping("1164", "912", "5:6")) + .add(new OutcomeMapping("1165", "914", "5:7")) + .add(new OutcomeMapping("1166", "916", "5:8")) + .add(new OutcomeMapping("1167", "918", "5:9")) + .add(new OutcomeMapping("1168", "920", "5:10")) + .add(new OutcomeMapping("1169", "922", "5:11")) + .add(new OutcomeMapping("1170", "924", "5:12")) + .add(new OutcomeMapping("1171", "926", "5:13")) + .add(new OutcomeMapping("1172", "928", "5:14")) + .add(new OutcomeMapping("1173", "930", "5:15")) + .add(new OutcomeMapping("1174", "932", "5:16")) + .add(new OutcomeMapping("1175", "934", "5:17")) + .add(new OutcomeMapping("1176", "936", "5:18")) + .add(new OutcomeMapping("1177", "938", "5:19")) + .add(new OutcomeMapping("1178", "940", "6:0")) + .add(new OutcomeMapping("1179", "942", "6:1")) + .add(new OutcomeMapping("1180", "944", "6:2")) + .add(new OutcomeMapping("1181", "946", "6:3")) + .add(new OutcomeMapping("1182", "948", "6:4")) + .add(new OutcomeMapping("1183", "950", "6:5")) + .add(new OutcomeMapping("1184", "952", "6:6")) + .add(new OutcomeMapping("1185", "954", "6:7")) + .add(new OutcomeMapping("1186", "956", "6:8")) + .add(new OutcomeMapping("1187", "958", "6:9")) + .add(new OutcomeMapping("1188", "960", "6:10")) + .add(new OutcomeMapping("1189", "962", "6:11")) + .add(new OutcomeMapping("1190", "964", "6:12")) + .add(new OutcomeMapping("1191", "966", "6:13")) + .add(new OutcomeMapping("1192", "968", "6:14")) + .add(new OutcomeMapping("1193", "970", "6:15")) + .add(new OutcomeMapping("1194", "972", "6:16")) + .add(new OutcomeMapping("1195", "974", "6:17")) + .add(new OutcomeMapping("1196", "976", "6:18")) + .add(new OutcomeMapping("1197", "978", "6:19")) + .add(new OutcomeMapping("1198", "980", "7:0")) + .add(new OutcomeMapping("1199", "982", "7:1")) + .add(new OutcomeMapping("1200", "984", "7:2")) + .add(new OutcomeMapping("1201", "986", "7:3")) + .add(new OutcomeMapping("1202", "988", "7:4")) + .add(new OutcomeMapping("1203", "990", "7:5")) + .add(new OutcomeMapping("1204", "992", "7:6")) + .add(new OutcomeMapping("1205", "994", "7:7")) + .add(new OutcomeMapping("1206", "996", "7:8")) + .add(new OutcomeMapping("1207", "998", "7:9")) + .add(new OutcomeMapping("1208", "1000", "7:10")) + .add(new OutcomeMapping("1209", "1002", "7:11")) + .add(new OutcomeMapping("1210", "1004", "7:12")) + .add(new OutcomeMapping("1211", "1006", "7:13")) + .add(new OutcomeMapping("1212", "1008", "7:14")) + .add(new OutcomeMapping("1213", "1010", "7:15")) + .add(new OutcomeMapping("1214", "1012", "7:16")) + .add(new OutcomeMapping("1215", "1014", "7:17")) + .add(new OutcomeMapping("1216", "1016", "7:18")) + .add(new OutcomeMapping("1217", "1018", "7:19")) + .add(new OutcomeMapping("1218", "1020", "8:0")) + .add(new OutcomeMapping("1219", "1022", "8:1")) + .add(new OutcomeMapping("1220", "1024", "8:2")) + .add(new OutcomeMapping("1221", "1026", "8:3")) + .add(new OutcomeMapping("1222", "1028", "8:4")) + .add(new OutcomeMapping("1223", "1030", "8:5")) + .add(new OutcomeMapping("1224", "1032", "8:6")) + .add(new OutcomeMapping("1225", "1034", "8:7")) + .add(new OutcomeMapping("1226", "1036", "8:8")) + .add(new OutcomeMapping("1227", "1038", "8:9")) + .add(new OutcomeMapping("1228", "1040", "8:10")) + .add(new OutcomeMapping("1229", "1042", "8:11")) + .add(new OutcomeMapping("1230", "1044", "8:12")) + .add(new OutcomeMapping("1231", "1046", "8:13")) + .add(new OutcomeMapping("1232", "1048", "8:14")) + .add(new OutcomeMapping("1233", "1050", "8:15")) + .add(new OutcomeMapping("1234", "1052", "8:16")) + .add(new OutcomeMapping("1235", "1054", "8:17")) + .add(new OutcomeMapping("1236", "1056", "8:18")) + .add(new OutcomeMapping("1237", "1058", "8:19")) + .add(new OutcomeMapping("1238", "1060", "9:0")) + .add(new OutcomeMapping("1239", "1062", "9:1")) + .add(new OutcomeMapping("1240", "1064", "9:2")) + .add(new OutcomeMapping("1241", "1066", "9:3")) + .add(new OutcomeMapping("1242", "1068", "9:4")) + .add(new OutcomeMapping("1243", "1070", "9:5")) + .add(new OutcomeMapping("1244", "1072", "9:6")) + .add(new OutcomeMapping("1245", "1074", "9:7")) + .add(new OutcomeMapping("1246", "1076", "9:8")) + .add(new OutcomeMapping("1247", "1078", "9:9")) + .add(new OutcomeMapping("1248", "1080", "9:10")) + .add(new OutcomeMapping("1249", "1082", "9:11")) + .add(new OutcomeMapping("1250", "1084", "9:12")) + .add(new OutcomeMapping("1251", "1086", "9:13")) + .add(new OutcomeMapping("1252", "1088", "9:14")) + .add(new OutcomeMapping("1253", "1090", "9:15")) + .add(new OutcomeMapping("1254", "1092", "9:16")) + .add(new OutcomeMapping("1255", "1094", "9:17")) + .add(new OutcomeMapping("1256", "1096", "9:18")) + .add(new OutcomeMapping("1257", "1098", "9:19")) + .add(new OutcomeMapping("1258", "1100", "10:0")) + .add(new OutcomeMapping("1259", "1102", "10:1")) + .add(new OutcomeMapping("1260", "1104", "10:2")) + .add(new OutcomeMapping("1261", "1106", "10:3")) + .add(new OutcomeMapping("1262", "1108", "10:4")) + .add(new OutcomeMapping("1263", "1110", "10:5")) + .add(new OutcomeMapping("1264", "1112", "10:6")) + .add(new OutcomeMapping("1265", "1114", "10:7")) + .add(new OutcomeMapping("1266", "1116", "10:8")) + .add(new OutcomeMapping("1267", "1118", "10:9")) + .add(new OutcomeMapping("1268", "1120", "10:10")) + .add(new OutcomeMapping("1269", "1122", "10:11")) + .add(new OutcomeMapping("1270", "1124", "10:12")) + .add(new OutcomeMapping("1271", "1126", "10:13")) + .add(new OutcomeMapping("1272", "1128", "10:14")) + .add(new OutcomeMapping("1273", "1130", "10:15")) + .add(new OutcomeMapping("1274", "1132", "10:16")) + .add(new OutcomeMapping("1275", "1134", "10:17")) + .add(new OutcomeMapping("1276", "1136", "10:18")) + .add(new OutcomeMapping("1277", "1138", "10:19")) + .add(new OutcomeMapping("1278", "1140", "11:0")) + .add(new OutcomeMapping("1279", "1142", "11:1")) + .add(new OutcomeMapping("1280", "1144", "11:2")) + .add(new OutcomeMapping("1281", "1146", "11:3")) + .add(new OutcomeMapping("1282", "1148", "11:4")) + .add(new OutcomeMapping("1283", "1150", "11:5")) + .add(new OutcomeMapping("1284", "1152", "11:6")) + .add(new OutcomeMapping("1285", "1154", "11:7")) + .add(new OutcomeMapping("1286", "1156", "11:8")) + .add(new OutcomeMapping("1287", "1158", "11:9")) + .add(new OutcomeMapping("1288", "1160", "11:10")) + .add(new OutcomeMapping("1289", "1162", "11:11")) + .add(new OutcomeMapping("1290", "1164", "11:12")) + .add(new OutcomeMapping("1291", "1166", "11:13")) + .add(new OutcomeMapping("1292", "1168", "11:14")) + .add(new OutcomeMapping("1293", "1170", "11:15")) + .add(new OutcomeMapping("1294", "1172", "11:16")) + .add(new OutcomeMapping("1295", "1174", "11:17")) + .add(new OutcomeMapping("1296", "1176", "11:18")) + .add(new OutcomeMapping("1297", "1178", "11:19")) + .add(new OutcomeMapping("1298", "1180", "12:0")) + .add(new OutcomeMapping("1299", "1182", "12:1")) + .add(new OutcomeMapping("1300", "1184", "12:2")) + .add(new OutcomeMapping("1301", "1186", "12:3")) + .add(new OutcomeMapping("1302", "1188", "12:4")) + .add(new OutcomeMapping("1303", "1190", "12:5")) + .add(new OutcomeMapping("1304", "1192", "12:6")) + .add(new OutcomeMapping("1305", "1194", "12:7")) + .add(new OutcomeMapping("1306", "1196", "12:8")) + .add(new OutcomeMapping("1307", "1198", "12:9")) + .add(new OutcomeMapping("1308", "1200", "12:10")) + .add(new OutcomeMapping("1309", "1202", "12:11")) + .add(new OutcomeMapping("1310", "1204", "12:12")) + .add(new OutcomeMapping("1311", "1206", "12:13")) + .add(new OutcomeMapping("1312", "1208", "12:14")) + .add(new OutcomeMapping("1313", "1210", "12:15")) + .add(new OutcomeMapping("1314", "1212", "12:16")) + .add(new OutcomeMapping("1315", "1214", "12:17")) + .add(new OutcomeMapping("1316", "1216", "12:18")) + .add(new OutcomeMapping("1317", "1218", "12:19")) + .add(new OutcomeMapping("1318", "1220", "13:0")) + .add(new OutcomeMapping("1319", "1222", "13:1")) + .add(new OutcomeMapping("1320", "1224", "13:2")) + .add(new OutcomeMapping("1321", "1226", "13:3")) + .add(new OutcomeMapping("1322", "1228", "13:4")) + .add(new OutcomeMapping("1323", "1230", "13:5")) + .add(new OutcomeMapping("1324", "1232", "13:6")) + .add(new OutcomeMapping("1325", "1234", "13:7")) + .add(new OutcomeMapping("1326", "1236", "13:8")) + .add(new OutcomeMapping("1327", "1238", "13:9")) + .add(new OutcomeMapping("1328", "1240", "13:10")) + .add(new OutcomeMapping("1329", "1242", "13:11")) + .add(new OutcomeMapping("1330", "1244", "13:12")) + .add(new OutcomeMapping("1331", "1246", "13:13")) + .add(new OutcomeMapping("1332", "1248", "13:14")) + .add(new OutcomeMapping("1333", "1250", "13:15")) + .add(new OutcomeMapping("1334", "1252", "13:16")) + .add(new OutcomeMapping("1335", "1254", "13:17")) + .add(new OutcomeMapping("1336", "1256", "13:18")) + .add(new OutcomeMapping("1337", "1258", "13:19")) + .add(new OutcomeMapping("1338", "1260", "14:0")) + .add(new OutcomeMapping("1339", "1262", "14:1")) + .add(new OutcomeMapping("1340", "1264", "14:2")) + .add(new OutcomeMapping("1341", "1266", "14:3")) + .add(new OutcomeMapping("1342", "1268", "14:4")) + .add(new OutcomeMapping("1343", "1270", "14:5")) + .add(new OutcomeMapping("1344", "1272", "14:6")) + .add(new OutcomeMapping("1345", "1274", "14:7")) + .add(new OutcomeMapping("1346", "1276", "14:8")) + .add(new OutcomeMapping("1347", "1278", "14:9")) + .add(new OutcomeMapping("1348", "1280", "14:10")) + .add(new OutcomeMapping("1349", "1282", "14:11")) + .add(new OutcomeMapping("1350", "1284", "14:12")) + .add(new OutcomeMapping("1351", "1286", "14:13")) + .add(new OutcomeMapping("1352", "1288", "14:14")) + .add(new OutcomeMapping("1353", "1290", "14:15")) + .add(new OutcomeMapping("1354", "1292", "14:16")) + .add(new OutcomeMapping("1355", "1294", "14:17")) + .add(new OutcomeMapping("1356", "1296", "14:18")) + .add(new OutcomeMapping("1357", "1298", "14:19")) + .add(new OutcomeMapping("1358", "1300", "15:0")) + .add(new OutcomeMapping("1359", "1302", "15:1")) + .add(new OutcomeMapping("1360", "1304", "15:2")) + .add(new OutcomeMapping("1361", "1306", "15:3")) + .add(new OutcomeMapping("1362", "1308", "15:4")) + .add(new OutcomeMapping("1363", "1310", "15:5")) + .add(new OutcomeMapping("1364", "1312", "15:6")) + .add(new OutcomeMapping("1365", "1314", "15:7")) + .add(new OutcomeMapping("1366", "1316", "15:8")) + .add(new OutcomeMapping("1367", "1318", "15:9")) + .add(new OutcomeMapping("1368", "1320", "15:10")) + .add(new OutcomeMapping("1369", "1322", "15:11")) + .add(new OutcomeMapping("1370", "1324", "15:12")) + .add(new OutcomeMapping("1371", "1326", "15:13")) + .add(new OutcomeMapping("1372", "1328", "15:14")) + .add(new OutcomeMapping("1373", "1330", "15:15")) + .add(new OutcomeMapping("1374", "1332", "15:16")) + .add(new OutcomeMapping("1375", "1334", "15:17")) + .add(new OutcomeMapping("1376", "1336", "15:18")) + .add(new OutcomeMapping("1377", "1338", "15:19")) + .add(new OutcomeMapping("1378", "1340", "16:0")) + .add(new OutcomeMapping("1379", "1342", "16:1")) + .add(new OutcomeMapping("1380", "1344", "16:2")) + .add(new OutcomeMapping("1381", "1346", "16:3")) + .add(new OutcomeMapping("1382", "1348", "16:4")) + .add(new OutcomeMapping("1383", "1350", "16:5")) + .add(new OutcomeMapping("1384", "1352", "16:6")) + .add(new OutcomeMapping("1385", "1354", "16:7")) + .add(new OutcomeMapping("1386", "1356", "16:8")) + .add(new OutcomeMapping("1387", "1358", "16:9")) + .add(new OutcomeMapping("1388", "1360", "16:10")) + .add(new OutcomeMapping("1389", "1362", "16:11")) + .add(new OutcomeMapping("1390", "1364", "16:12")) + .add(new OutcomeMapping("1391", "1366", "16:13")) + .add(new OutcomeMapping("1392", "1368", "16:14")) + .add(new OutcomeMapping("1393", "1370", "16:15")) + .add(new OutcomeMapping("1394", "1372", "16:16")) + .add(new OutcomeMapping("1395", "1374", "16:17")) + .add(new OutcomeMapping("1396", "1376", "16:18")) + .add(new OutcomeMapping("1397", "1378", "16:19")) + .add(new OutcomeMapping("1398", "1380", "17:0")) + .add(new OutcomeMapping("1399", "1382", "17:1")) + .add(new OutcomeMapping("1400", "1384", "17:2")) + .add(new OutcomeMapping("1401", "1386", "17:3")) + .add(new OutcomeMapping("1402", "1388", "17:4")) + .add(new OutcomeMapping("1403", "1390", "17:5")) + .add(new OutcomeMapping("1404", "1392", "17:6")) + .add(new OutcomeMapping("1405", "1394", "17:7")) + .add(new OutcomeMapping("1406", "1396", "17:8")) + .add(new OutcomeMapping("1407", "1398", "17:9")) + .add(new OutcomeMapping("1408", "1400", "17:10")) + .add(new OutcomeMapping("1409", "1402", "17:11")) + .add(new OutcomeMapping("1410", "1404", "17:12")) + .add(new OutcomeMapping("1411", "1406", "17:13")) + .add(new OutcomeMapping("1412", "1408", "17:14")) + .add(new OutcomeMapping("1413", "1410", "17:15")) + .add(new OutcomeMapping("1414", "1412", "17:16")) + .add(new OutcomeMapping("1415", "1414", "17:17")) + .add(new OutcomeMapping("1416", "1416", "17:18")) + .add(new OutcomeMapping("1417", "1418", "17:19")) + .add(new OutcomeMapping("1418", "1420", "18:0")) + .add(new OutcomeMapping("1419", "1422", "18:1")) + .add(new OutcomeMapping("1420", "1424", "18:2")) + .add(new OutcomeMapping("1421", "1426", "18:3")) + .add(new OutcomeMapping("1422", "1428", "18:4")) + .add(new OutcomeMapping("1423", "1430", "18:5")) + .add(new OutcomeMapping("1424", "1432", "18:6")) + .add(new OutcomeMapping("1425", "1434", "18:7")) + .add(new OutcomeMapping("1426", "1436", "18:8")) + .add(new OutcomeMapping("1427", "1438", "18:9")) + .add(new OutcomeMapping("1428", "1440", "18:10")) + .add(new OutcomeMapping("1429", "1442", "18:11")) + .add(new OutcomeMapping("1430", "1444", "18:12")) + .add(new OutcomeMapping("1431", "1446", "18:13")) + .add(new OutcomeMapping("1432", "1448", "18:14")) + .add(new OutcomeMapping("1433", "1450", "18:15")) + .add(new OutcomeMapping("1434", "1452", "18:16")) + .add(new OutcomeMapping("1435", "1454", "18:17")) + .add(new OutcomeMapping("1436", "1456", "18:18")) + .add(new OutcomeMapping("1437", "1458", "18:19")) + .add(new OutcomeMapping("1438", "1460", "19:0")) + .add(new OutcomeMapping("1439", "1462", "19:1")) + .add(new OutcomeMapping("1440", "1464", "19:2")) + .add(new OutcomeMapping("1441", "1466", "19:3")) + .add(new OutcomeMapping("1442", "1468", "19:4")) + .add(new OutcomeMapping("1443", "1470", "19:5")) + .add(new OutcomeMapping("1444", "1472", "19:6")) + .add(new OutcomeMapping("1445", "1474", "19:7")) + .add(new OutcomeMapping("1446", "1476", "19:8")) + .add(new OutcomeMapping("1447", "1478", "19:9")) + .add(new OutcomeMapping("1448", "1480", "19:10")) + .add(new OutcomeMapping("1449", "1482", "19:11")) + .add(new OutcomeMapping("1450", "1484", "19:12")) + .add(new OutcomeMapping("1451", "1486", "19:13")) + .add(new OutcomeMapping("1452", "1488", "19:14")) + .add(new OutcomeMapping("1453", "1490", "19:15")) + .add(new OutcomeMapping("1454", "1492", "19:16")) + .add(new OutcomeMapping("1455", "1494", "19:17")) + .add(new OutcomeMapping("1456", "1496", "19:18")) + .add(new OutcomeMapping("1457", "1498", "19:19")) + .build() + .stream() + .collect(toMap(OutcomeMapping::getOutcomeId, om -> om)); + + // + + public static DescMarket correctScoreFlexMarketDescription(Locale language) { + return correctScoreFlexMarketDescription(MarketTranslation.getFor(language)); + } + + private static DescMarket correctScoreFlexMarketDescription( + CorrectScoreFlex.MarketTranslation translation + ) { + DescMarket market = new DescMarket(); + market.setId(CORRECT_SCORE_FLEX_SCORE_MARKET_ID); + market.setName(translation.marketName); + market.setGroups("all|score|regular_play"); + market.setSpecifiers(scoreSpecifier()); + market.setAttributes(flexScoreAttributes()); + populateOutcomes(from(FlexScoreMarketIds.correctScoreFlexScoreMarket()), to(market)); + market.setMappings(mappings()); + return market; + } + + private static DescSpecifiers scoreSpecifier() { + DescSpecifiers.Specifier specifier = new DescSpecifiers.Specifier(); + specifier.setName("score"); + specifier.setType("string"); + specifier.setDescription("current score in match"); + DescSpecifiers descSpecifiers = new DescSpecifiers(); + descSpecifiers.getSpecifier().add(specifier); + return descSpecifiers; + } + + private static Attributes flexScoreAttributes() { + Attributes attributes = new Attributes(); + Attributes.Attribute flexScoreAttribute = new Attributes.Attribute(); + flexScoreAttribute.setName("is_flex_score"); + flexScoreAttribute.setDescription("Outcomes should be adjusted according to score specifier"); + attributes.getAttribute().add(flexScoreAttribute); + return attributes; + } + + private static Mappings mappings() { + val mappings = new Mappings(); + populateOutcomeMappings(from(FlexScoreMarketIds.correctScoreFlexScoreMarket()), to(mappings)); + return mappings; + } + + private static void populateOutcomeMappings(MarketVariant market, Mappings mappings) { + market + .mappings() + .forEach((mapping, outcomes) -> { + List mappingOutcomes = outcomes + .stream() + .map(o -> + MarketTranslation.EN.outcomeMappingTranslations.get(o).toSapiOutcomeMapping() + ) + .collect(Collectors.toList()); + Mappings.Mapping m = mapping.toSapiMapping(mappingOutcomes); + mappings.getMapping().add(m); + }); + } + + private static void populateOutcomes(MarketVariant fromVariant, DescMarket toMarket) { + val outcomes = new DescOutcomes(); + populateOutcomes(fromVariant, to(outcomes)); + toMarket.setOutcomes(outcomes); + } + + private static void populateOutcomes(MarketVariant fromVariant, DescOutcomes toOutcomes) { + fromVariant + .outcomeIds() + .forEach(id -> + toOutcomes + .getOutcome() + .add(outcome(id, CorrectScoreFlex.MarketTranslation.EN.outcomeTranslations.get(id))) + ); + } + + private static DescOutcomes.Outcome outcome(String id, String name) { + val outcome = new DescOutcomes.Outcome(); + outcome.setId(id); + outcome.setName(name); + return outcome; + } + + @Getter + @RequiredArgsConstructor + private enum MarketTranslation { + EN( + Locale.ENGLISH, + "Correct score [{score}]", + ENGLISH_TRANSLATIONS, + OUTCOME_MAPPING_ENGLISH_TRANSLATIONS + ); + + private final Locale language; + private final String marketName; + private final Map outcomeTranslations; + private final Map outcomeMappingTranslations; + + public static CorrectScoreFlex.MarketTranslation getFor(Locale language) { + return stream(CorrectScoreFlex.MarketTranslation.values()) + .filter(translation -> translation.language.equals(language)) + .findFirst() + .orElseThrow(() -> new RuntimeException(LANGUAGE_NOT_SUPPORTED_BY_TEST_FIXTURE)); + } } } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiVariantDescriptions.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiVariantDescriptions.java new file mode 100644 index 00000000..1220ebfc --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SapiVariantDescriptions.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn; + +import com.google.common.collect.ImmutableMap; +import com.sportradar.uf.sportsapi.datamodel.DescVariant; +import com.sportradar.uf.sportsapi.datamodel.DescVariantOutcomes; +import com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds; +import com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds; +import java.util.Locale; +import java.util.Map; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +public final class SapiVariantDescriptions { + + private static void expectSameOutcomeIds(TranslatedOutcomes marketTranslation, MarketVariant variant) { + if (marketTranslation.getOutcomeTranslations().size() != variant.outcomeIds().size()) { + throw new IllegalArgumentException("The number of outcome ids and translations must be the same"); + } + if (!marketTranslation.getOutcomeTranslations().keySet().containsAll(variant.outcomeIds())) { + throw new IllegalArgumentException("The outcome ids must be the same"); + } + } + + public static class ExactGoals { + + private static final Map ENGLISH_TRANSLATIONS = ImmutableMap + .builder() + .put("sr:exact_goals:5+:1336", "0") + .put("sr:exact_goals:5+:1337", "1") + .put("sr:exact_goals:5+:1338", "2") + .put("sr:exact_goals:5+:1339", "3") + .put("sr:exact_goals:5+:1340", "4") + .put("sr:exact_goals:5+:1341", "5+") + .build(); + + public static DescVariant fivePlusVariantDescription() { + DescVariant variant = new DescVariant(); + variant.setId("sr:exact_goals:5+"); + variant.setOutcomes(populateOutcomesValidating()); + return variant; + } + + private static DescVariantOutcomes populateOutcomesValidating() { + expectSameOutcomeIds(MarketTranslation.EN, ExactGoalsMarketIds.fivePlusVariant()); + return populateOutcomes(); + } + + private static DescVariantOutcomes populateOutcomes() { + DescVariantOutcomes outcomes = new DescVariantOutcomes(); + ExactGoalsMarketIds + .fivePlusVariant() + .outcomeIds() + .forEach(id -> + outcomes + .getOutcome() + .add(outcome(id, MarketTranslation.EN.getOutcomeTranslations().get(id))) + ); + return outcomes; + } + + private static DescVariantOutcomes.Outcome outcome(String id, String name) { + DescVariantOutcomes.Outcome outcome = new DescVariantOutcomes.Outcome(); + outcome.setId(id); + outcome.setName(name); + return outcome; + } + + @RequiredArgsConstructor + @Getter + private enum MarketTranslation implements TranslatedOutcomes { + EN(Locale.ENGLISH, ENGLISH_TRANSLATIONS); + + private final Locale language; + private final Map outcomeTranslations; + } + } + + interface TranslatedOutcomes { + Map getOutcomeTranslations(); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkConnectionIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkConnectionIT.java index ec817760..83d837b3 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkConnectionIT.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkConnectionIT.java @@ -108,9 +108,9 @@ public void setup() throws Exception { scenario = new Scenario.Factory(sdkListener); feed = new TestFeed(sdkListener, config, sdkListener); GlobalVariables variables = new GlobalVariables(); - - feedMessageBuilder = - new FeedMessageBuilder(ProducerId.LIVE_ODDS, com.sportradar.unifiedodds.sdk.conn.SportEvent.ANY); + variables.setProducer(ProducerId.LIVE_ODDS); + variables.setSportEventUrn(com.sportradar.unifiedodds.sdk.conn.SportEvent.ANY); + feedMessageBuilder = new FeedMessageBuilder(variables); // establish connection to the test rabbit server for rabbit producer TestProducersProvider testProducersProvider = new TestProducersProvider(); diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkSetup.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkSetup.java index 9e40b7a8..a04ab048 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkSetup.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkSetup.java @@ -71,6 +71,21 @@ public SdkSetup withDefaultLanguage(Locale language) { return this; } + public UofSdk withoutFeed() throws InitException { + UofConfiguration config = UofSdk + .getUofConfigurationBuilder() + .setAccessToken(sdkCredentials.getUsername()) + .selectCustom() + .setApiUseSsl(false) + .setApiHost(sportsApiBaseUrl.get()) + .setDefaultLanguage(defaultLanguage.orElse(Languages.any())) + .setExceptionHandlingStrategy(exceptionHandlingStrategy.orElse(anyErrorHandlingStrategy())) + .setNodeId(nodeId) + .build(); + + return createSdk(config); + } + public UofSdk withOpenedFeed() throws InitException { UofConfiguration config = UofSdk .getUofConfigurationBuilder() diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SpecifierDescriptions.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SpecifierDescriptions.java new file mode 100644 index 00000000..2bd1f4ae --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SpecifierDescriptions.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn; + +import com.sportradar.uf.sportsapi.datamodel.DescSpecifiers; +import com.sportradar.unifiedodds.sdk.impl.UnifiedFeedConstants; + +public class SpecifierDescriptions { + + public static DescSpecifiers variant() { + DescSpecifiers setSpecifiers = new DescSpecifiers(); + setSpecifiers + .getSpecifier() + .add(specifier(UnifiedFeedConstants.VARIANT_DESCRIPTION_NAME, "variable_text")); + return setSpecifiers; + } + + public static DescSpecifiers variantAndVersion() { + DescSpecifiers setSpecifiers = new DescSpecifiers(); + setSpecifiers + .getSpecifier() + .add(specifier(UnifiedFeedConstants.VARIANT_DESCRIPTION_NAME, "variable_text")); + setSpecifiers.getSpecifier().add(specifier("version", "string")); + return setSpecifiers; + } + + private static DescSpecifiers.Specifier specifier(String name, String type) { + DescSpecifiers.Specifier specifier = new DescSpecifiers.Specifier(); + specifier.setName(name); + specifier.setType(type); + return specifier; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/Sport.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/Sport.java index 29bc649c..a6fd42bc 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/Sport.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/Sport.java @@ -6,7 +6,8 @@ import com.sportradar.utils.Urn; public enum Sport { - FOOTBALL(Urn.parse("sr:sport:1")); + FOOTBALL(Urn.parse("sr:sport:1")), + FUTSAL(Urn.parse("sr:sport:29")); private final Urn urn; diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/UfMarkets.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/UfMarkets.java index 728d5e6d..7f1bbbf0 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/UfMarkets.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/UfMarkets.java @@ -3,12 +3,19 @@ */ package com.sportradar.unifiedodds.sdk.conn; -import static com.sportradar.unifiedodds.sdk.conn.OddEvenMarket.EVEN_OUTCOME_ID; -import static com.sportradar.unifiedodds.sdk.conn.OddEvenMarket.ODD_OUTCOME_ID; import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.UfOddsChangeOutcomeBuilder.activeOutcome; +import static com.sportradar.unifiedodds.sdk.conn.marketids.ExactGoalsMarketIds.EXACT_GOALS_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FlexScoreMarketIds.*; +import static com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds.FREE_TEXT_MARKET_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds.*; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Prepositions.from; +import static com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage.Prepositions.to; import static com.sportradar.utils.generic.testing.RandomObjectPicker.pickOneRandomlyFrom; import com.sportradar.uf.datamodel.*; +import com.sportradar.unifiedodds.sdk.conn.marketids.FlexScoreMarketIds; +import com.sportradar.unifiedodds.sdk.conn.marketids.FreeTextMarketIds; +import com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds; import com.sportradar.utils.domain.feedmessages.markets.TeamIndicators; import lombok.val; @@ -16,18 +23,18 @@ public class UfMarkets { public static class Simple { - public static UfMarket oddEven() { + public static UfMarket oddEvenMarket() { UfMarket market = new UfMarket(); - market.setId(OddEvenMarket.ID); + market.setId(ODD_EVEN_MARKET_ID); return market; } } public static class WithOdds { - public static UfOddsChangeMarket oddEven() { + public static UfOddsChangeMarket oddEvenMarket() { UfOddsChangeMarket market = new UfOddsChangeMarket(); - market.setId(OddEvenMarket.ID); + market.setId(ODD_EVEN_MARKET_ID); market.getOutcome().add(activeOutcome().withId(ODD_OUTCOME_ID)); market.getOutcome().add(activeOutcome().withId(EVEN_OUTCOME_ID)); @@ -35,7 +42,7 @@ public static UfOddsChangeMarket oddEven() { return market; } - public static UfOddsChangeMarket anytimeGoalscorer() { + public static UfOddsChangeMarket anytimeGoalscorerMarket() { final int anytimeGoalscorerMarketId = 40; UfOddsChangeMarket market = new UfOddsChangeMarket(); market.setId(anytimeGoalscorerMarketId); @@ -52,6 +59,50 @@ public static UfOddsChangeMarket anytimeGoalscorer() { return market; } + public static UfOddsChangeMarket exactGoalsMarket(MarketVariant variant) { + UfOddsChangeMarket market = new UfOddsChangeMarket(); + market.setId(EXACT_GOALS_MARKET_ID); + market.setSpecifiers("variant=" + variant.id()); + populateOutcomeIds(from(variant), to(market)); + return market; + } + + private static void populateOutcomeIds(MarketVariant fromVariant, UfOddsChangeMarket toMarket) { + fromVariant.outcomeIds().forEach(id -> toMarket.getOutcome().add(activeOutcome().withId(id))); + } + + public static UfOddsChangeMarket nascarOutrightsMarket() { + UfOddsChangeMarket market = new UfOddsChangeMarket(); + market.setId(FREE_TEXT_MARKET_ID); + market.setSpecifiers("variant=" + FreeTextMarketIds.nascarOutrightsVariant().id()); + populateOutcomeIds(from(FreeTextMarketIds.nascarOutrightsVariant()), to(market)); + return market; + } + + public static UfOddsChangeMarket nascarOutrightsOddEvenMarket() { + UfOddsChangeMarket market = new UfOddsChangeMarket(); + market.setId(FREE_TEXT_MARKET_ID); + market.setSpecifiers("variant=" + FreeTextMarketIds.nascarOutrightsOddEvenVariant().id()); + populateOutcomeIds(from(FreeTextMarketIds.nascarOutrightsOddEvenVariant()), to(market)); + return market; + } + + public static UfOddsChangeMarket correctScoreFlexMarket() { + UfOddsChangeMarket market = new UfOddsChangeMarket(); + market.setId(CORRECT_SCORE_FLEX_SCORE_MARKET_ID); + market.setSpecifiers(THREE_TO_ONE_SCORE_SPECIFIER); + populateOutcomeIds(from(FlexScoreMarketIds.correctScoreFlexScoreMarket()), to(market)); + return market; + } + + public static UfOddsChangeMarket correctScoreFlexMarket(int currentHomeScore, int currentAwayScore) { + UfOddsChangeMarket market = new UfOddsChangeMarket(); + market.setId(CORRECT_SCORE_FLEX_SCORE_MARKET_ID); + market.setSpecifiers(String.format(SCORE_SPECIFIER, currentHomeScore, currentAwayScore)); + populateOutcomeIds(from(FlexScoreMarketIds.correctScoreFlexScoreMarket()), to(market)); + return market; + } + public static class UfOddsChangeOutcomeBuilder { private UfOddsChangeOutcomeBuilder() {} @@ -68,7 +119,7 @@ public UfOddsChangeMarket.UfOutcome withId(String id) { return outcome; } - public UfOddsChangeMarket.UfOutcome withAnyTeamAndId(String id) { + private UfOddsChangeMarket.UfOutcome withAnyTeamAndId(String id) { val outcome = withId(id); outcome.setTeam(pickOneRandomlyFrom(TeamIndicators.values()).getValue()); return outcome; @@ -78,9 +129,9 @@ public UfOddsChangeMarket.UfOutcome withAnyTeamAndId(String id) { public static class WithSettlementOutcomes { - public static UfBetSettlementMarket oddEvenWhereWonOdd() { + public static UfBetSettlementMarket oddEvenMarketWhereWonOdd() { UfBetSettlementMarket market = new UfBetSettlementMarket(); - market.setId(OddEvenMarket.ID); + market.setId(ODD_EVEN_MARKET_ID); market.getOutcome().add(UfBetSettlementOutcomeBuilder.activeOutcome().wonWithId(ODD_OUTCOME_ID)); market.getOutcome().add(UfBetSettlementOutcomeBuilder.activeOutcome().wonWithId(EVEN_OUTCOME_ID)); @@ -96,19 +147,12 @@ public static UfBetSettlementOutcomeBuilder activeOutcome() { return new UfBetSettlementOutcomeBuilder(); } - public UfBetSettlementMarket.UfOutcome wonWithId(String id) { + private UfBetSettlementMarket.UfOutcome wonWithId(String id) { UfBetSettlementMarket.UfOutcome outcome = new UfBetSettlementMarket.UfOutcome(); outcome.setId(id); outcome.setResult(UfResult.WON); return outcome; } - - public UfBetSettlementMarket.UfOutcome lostWithId(String id) { - UfBetSettlementMarket.UfOutcome outcome = new UfBetSettlementMarket.UfOutcome(); - outcome.setId(id); - outcome.setResult(UfResult.LOST); - return outcome; - } } } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/WaiterForSingleMessage.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/WaiterForSingleMessage.java index 0e2745d0..08d48b58 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/WaiterForSingleMessage.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/WaiterForSingleMessage.java @@ -33,7 +33,25 @@ public OddsChange theOnlyOdd return allOddsChange.get(0); } + public OddsChange secondOddsChange() { + final int tenForSlowMachines = 10; + Awaitility.await().atMost(tenForSlowMachines, SECONDS).until(multipleOddsChangeMessageReceived()); + List> allOddsChange = new ArrayList<>( + messagesStorage.findAllOddsChange() + ); + if (allOddsChange.size() <= 1) { + throw new IllegalStateException( + "Expected at least 2 odds change message, but found " + allOddsChange.size() + ); + } + return allOddsChange.get(1); + } + private Callable anyOddsChangeMessageReceived() { return () -> !messagesStorage.findAllOddsChange().isEmpty(); } + + private Callable multipleOddsChangeMessageReceived() { + return () -> messagesStorage.findAllOddsChange().size() > 1; + } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/ChampionshipFreeTextMarketIds.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/ChampionshipFreeTextMarketIds.java new file mode 100644 index 00000000..5fe8e4ca --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/ChampionshipFreeTextMarketIds.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn.marketids; + +import static java.util.Arrays.asList; + +import com.sportradar.unifiedodds.sdk.conn.MarketVariant; +import java.util.List; + +public class ChampionshipFreeTextMarketIds { + + public static final int CHAMPIONSHIP_FREE_TEXT_MARKET_ID = 534; + + public static final int CHAMPIONSHIP_FREE_TEXT_OPEN_MARKET_ID = 906; + + public static NflAfcConferenceOutrightVariant nflAfcConferenceOutrightsVariant() { + return new NflAfcConferenceOutrightVariant(); + } + + public static class NflAfcConferenceOutrightVariant implements MarketVariant { + + @Override + public String id() { + return "pre:markettext:40841"; + } + + @Override + public List outcomeIds() { + return asList( + "pre:outcometext:35260", + "pre:outcometext:33133", + "pre:outcometext:35270", + "pre:outcometext:35278", + "pre:outcometext:119349", + "pre:outcometext:33135", + "pre:outcometext:33136", + "pre:outcometext:35274", + "pre:outcometext:35266", + "pre:outcometext:35254", + "pre:outcometext:35250", + "pre:outcometext:33138", + "pre:outcometext:35264", + "pre:outcometext:35249", + "pre:outcometext:35253", + "pre:outcometext:35263" + ); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/ExactGoalsMarketIds.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/ExactGoalsMarketIds.java new file mode 100644 index 00000000..c1217e9c --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/ExactGoalsMarketIds.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn.marketids; + +import static java.util.Arrays.asList; + +import com.sportradar.unifiedodds.sdk.conn.MarketVariant; +import java.util.List; + +public final class ExactGoalsMarketIds { + + public static final int EXACT_GOALS_MARKET_ID = 21; + + private ExactGoalsMarketIds() {} + + public static FivePlusVariant fivePlusVariant() { + return new FivePlusVariant(); + } + + public static class FivePlusVariant implements MarketVariant { + + @Override + public String id() { + return "sr:exact_goals:5+"; + } + + @Override + public List outcomeIds() { + return asList( + "sr:exact_goals:5+:1336", + "sr:exact_goals:5+:1337", + "sr:exact_goals:5+:1338", + "sr:exact_goals:5+:1339", + "sr:exact_goals:5+:1340", + "sr:exact_goals:5+:1341" + ); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/FlexScoreMarketIds.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/FlexScoreMarketIds.java new file mode 100644 index 00000000..0d08add8 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/FlexScoreMarketIds.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn.marketids; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.sportradar.unifiedodds.sdk.conn.MarketVariant; +import com.sportradar.unifiedodds.sdk.conn.Sport; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import lombok.val; + +public class FlexScoreMarketIds { + + public static final int CORRECT_SCORE_FLEX_SCORE_MARKET_ID = 401; + + public static final String SCORE_SPECIFIER = "score=%d:%d"; + public static final String THREE_TO_ONE_SCORE_SPECIFIER = String.format(SCORE_SPECIFIER, 3, 1); + public static final String ZERO_TO_ZERO_OUTCOME_ID = "1058"; + + public static MarketVariant correctScoreFlexScoreMarket() { + return new CorrectScoreFlexScoreMarket(); + } + + private static final class CorrectScoreFlexScoreMarket implements MarketVariant { + + @Override + public String id() { + return String.valueOf(CORRECT_SCORE_FLEX_SCORE_MARKET_ID); + } + + @Override + @SuppressWarnings("MethodLength") + public List outcomeIds() { + return Arrays.asList( + "1058", + "1059", + "1060", + "1061", + "1062", + "1063", + "1064", + "1065", + "1066", + "1067", + "1068", + "1069", + "1070", + "1071", + "1072", + "1073", + "1074", + "1075", + "1076", + "1077", + "1078", + "1079", + "1080", + "1081", + "1082", + "1083", + "1084", + "1085", + "1086", + "1087", + "1088", + "1089", + "1090", + "1091", + "1092", + "1093", + "1094", + "1095", + "1096", + "1097", + "1098", + "1099", + "1100", + "1101", + "1102", + "1103", + "1104", + "1105", + "1106", + "1107", + "1108", + "1109", + "1110", + "1111", + "1112", + "1113", + "1114", + "1115", + "1116", + "1117", + "1118", + "1119", + "1120", + "1121", + "1122", + "1123", + "1124", + "1125", + "1126", + "1127", + "1128", + "1129", + "1130", + "1131", + "1132", + "1133", + "1134", + "1135", + "1136", + "1137", + "1138", + "1139", + "1140", + "1141", + "1142", + "1143", + "1144", + "1145", + "1146", + "1147", + "1148", + "1149", + "1150", + "1151", + "1152", + "1153", + "1154", + "1155", + "1156", + "1157", + "1158", + "1159", + "1160", + "1161", + "1162", + "1163", + "1164", + "1165", + "1166", + "1167", + "1168", + "1169", + "1170", + "1171", + "1172", + "1173", + "1174", + "1175", + "1176", + "1177", + "1178", + "1179", + "1180", + "1181", + "1182", + "1183", + "1184", + "1185", + "1186", + "1187", + "1188", + "1189", + "1190", + "1191", + "1192", + "1193", + "1194", + "1195", + "1196", + "1197", + "1198", + "1199", + "1200", + "1201", + "1202", + "1203", + "1204", + "1205", + "1206", + "1207", + "1208", + "1209", + "1210", + "1211", + "1212", + "1213", + "1214", + "1215", + "1216", + "1217", + "1218", + "1219", + "1220", + "1221", + "1222", + "1223", + "1224", + "1225", + "1226", + "1227", + "1228", + "1229", + "1230", + "1231", + "1232", + "1233", + "1234", + "1235", + "1236", + "1237", + "1238", + "1239", + "1240", + "1241", + "1242", + "1243", + "1244", + "1245", + "1246", + "1247", + "1248", + "1249", + "1250", + "1251", + "1252", + "1253", + "1254", + "1255", + "1256", + "1257", + "1258", + "1259", + "1260", + "1261", + "1262", + "1263", + "1264", + "1265", + "1266", + "1267", + "1268", + "1269", + "1270", + "1271", + "1272", + "1273", + "1274", + "1275", + "1276", + "1277", + "1278", + "1279", + "1280", + "1281", + "1282", + "1283", + "1284", + "1285", + "1286", + "1287", + "1288", + "1289", + "1290", + "1291", + "1292", + "1293", + "1294", + "1295", + "1296", + "1297", + "1298", + "1299", + "1300", + "1301", + "1302", + "1303", + "1304", + "1305", + "1306", + "1307", + "1308", + "1309", + "1310", + "1311", + "1312", + "1313", + "1314", + "1315", + "1316", + "1317", + "1318", + "1319", + "1320", + "1321", + "1322", + "1323", + "1324", + "1325", + "1326", + "1327", + "1328", + "1329", + "1330", + "1331", + "1332", + "1333", + "1334", + "1335", + "1336", + "1337", + "1338", + "1339", + "1340", + "1341", + "1342", + "1343", + "1344", + "1345", + "1346", + "1347", + "1348", + "1349", + "1350", + "1351", + "1352", + "1353", + "1354", + "1355", + "1356", + "1357", + "1358", + "1359", + "1360", + "1361", + "1362", + "1363", + "1364", + "1365", + "1366", + "1367", + "1368", + "1369", + "1370", + "1371", + "1372", + "1373", + "1374", + "1375", + "1376", + "1377", + "1378", + "1379", + "1380", + "1381", + "1382", + "1383", + "1384", + "1385", + "1386", + "1387", + "1388", + "1389", + "1390", + "1391", + "1392", + "1393", + "1394", + "1395", + "1396", + "1397", + "1398", + "1399", + "1400", + "1401", + "1402", + "1403", + "1404", + "1405", + "1406", + "1407", + "1408", + "1409", + "1410", + "1411", + "1412", + "1413", + "1414", + "1415", + "1416", + "1417", + "1418", + "1419", + "1420", + "1421", + "1422", + "1423", + "1424", + "1425", + "1426", + "1427", + "1428", + "1429", + "1430", + "1431", + "1432", + "1433", + "1434", + "1435", + "1436", + "1437", + "1438", + "1439", + "1440", + "1441", + "1442", + "1443", + "1444", + "1445", + "1446", + "1447", + "1448", + "1449", + "1450", + "1451", + "1452", + "1453", + "1454", + "1455", + "1456", + "1457" + ); + } + + @Override + public Map> mappings() { + val mapping = new Mapping(1, "1|4", Sport.FUTSAL.getUrn().toString(), "8:188", "{score}"); + return ImmutableMap.>builder().put(mapping, outcomeIds()).build(); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/FreeTextMarketIds.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/FreeTextMarketIds.java new file mode 100644 index 00000000..ddb59ea2 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/FreeTextMarketIds.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn.marketids; + +import static java.util.Arrays.asList; + +import com.sportradar.unifiedodds.sdk.conn.MarketVariant; +import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithOdds; +import com.sportradar.unifiedodds.sdk.oddsentities.OutcomeOdds; +import java.util.List; + +public class FreeTextMarketIds { + + public static final int FREE_TEXT_MARKET_ID = 559; + + public static NascarOutrightsWinnerVariant nascarOutrightsVariant() { + return new NascarOutrightsWinnerVariant(); + } + + public static NascarOutrightsOddEvenVariant nascarOutrightsOddEvenVariant() { + return new NascarOutrightsOddEvenVariant(); + } + + public static class NascarOutrightsWinnerVariant implements MarketVariant { + + @Override + public String id() { + return "pre:markettext:233945"; + } + + @Override + public List outcomeIds() { + return asList( + "pre:outcometext:9832848", + "pre:outcometext:11154385", + "pre:outcometext:8191791", + "pre:outcometext:9840952", + "pre:outcometext:9513940", + "pre:outcometext:6258421", + "pre:outcometext:7084505", + "pre:outcometext:6569371", + "pre:outcometext:505632", + "pre:outcometext:6621072", + "pre:outcometext:1014491", + "pre:outcometext:318760", + "pre:outcometext:7125706", + "pre:outcometext:5520588", + "pre:outcometext:7458692", + "pre:outcometext:12572116", + "pre:outcometext:16714708", + "pre:outcometext:1277357", + "pre:outcometext:1238461", + "pre:outcometext:7696944", + "pre:outcometext:11787001", + "pre:outcometext:11160813", + "pre:outcometext:5695824", + "pre:outcometext:6593846", + "pre:outcometext:6944491", + "pre:outcometext:6864419", + "pre:outcometext:8217403", + "pre:outcometext:8820457", + "pre:outcometext:7401353", + "pre:outcometext:9065140", + "pre:outcometext:9110228", + "pre:outcometext:7125704", + "pre:outcometext:9832847", + "pre:outcometext:9861894", + "pre:outcometext:9840951", + "pre:outcometext:318761" + ); + } + } + + public static class NascarOutrightsOddEvenVariant implements MarketVariant { + + public static final String NASCAR_ODD_OUTCOME_ID = "pre:outcometext:15093909"; + public static final String NASCAR_EVEN_OUTCOME_ID = "pre:outcometext:15093907"; + + public static OutcomeOdds nascarEvenOutcomeOf(MarketWithOdds market) { + return market + .getOutcomeOdds() + .stream() + .filter(NascarOutrightsOddEvenVariant::isNascarEven) + .findFirst() + .get(); + } + + public static boolean isNascarEven(OutcomeOdds outcome) { + return outcome.getId().equals(NASCAR_EVEN_OUTCOME_ID); + } + + @Override + public String id() { + return "pre:markettext:234285"; + } + + @Override + public List outcomeIds() { + return asList(NASCAR_ODD_OUTCOME_ID, NASCAR_EVEN_OUTCOME_ID); + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/Markets.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/Markets.java new file mode 100644 index 00000000..83c6469c --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/Markets.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn.marketids; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; + +import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithOdds; +import com.sportradar.unifiedodds.sdk.oddsentities.OutcomeOdds; +import java.util.List; +import java.util.stream.Collectors; + +public class Markets { + + public static OutcomeOdds findOutcome(String outcomeId, MarketWithOdds market) { + List outcomes = market + .getOutcomeOdds() + .stream() + .filter(o -> outcomeId.equals(o.getId())) + .collect(toList()); + assertThat(outcomes).hasSize(1); + return outcomes.get(0); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/OddEvenMarket.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/OddEvenMarketIds.java similarity index 69% rename from sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/OddEvenMarket.java rename to sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/OddEvenMarketIds.java index 356b086c..6448dc98 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/OddEvenMarket.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/OddEvenMarketIds.java @@ -1,29 +1,29 @@ /* * Copyright (C) Sportradar AG. See LICENSE for full license governing this code */ -package com.sportradar.unifiedodds.sdk.conn; +package com.sportradar.unifiedodds.sdk.conn.marketids; + +import static com.sportradar.utils.generic.testing.RandomObjectPicker.pickOneRandomlyFrom; import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithOdds; import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithProbabilities; import com.sportradar.unifiedodds.sdk.oddsentities.OutcomeOdds; import com.sportradar.unifiedodds.sdk.oddsentities.OutcomeProbabilities; -public final class OddEvenMarket { +public final class OddEvenMarketIds { - public static final int ID = 26; + public static final int ODD_EVEN_MARKET_ID = 26; public static final String ODD_OUTCOME_ID = "70"; public static final String EVEN_OUTCOME_ID = "72"; - private OddEvenMarket() {} - public static OutcomeOdds evenOutcomeOf(MarketWithOdds market) { - return market.getOutcomeOdds().stream().filter(OddEvenMarket::isEven).findFirst().get(); + return market.getOutcomeOdds().stream().filter(OddEvenMarketIds::isEven).findFirst().get(); } public static OutcomeProbabilities evenOutcomeOf(MarketWithProbabilities market) { - return market.getOutcomeProbabilities().stream().filter(OddEvenMarket::isEven).findFirst().get(); + return market.getOutcomeProbabilities().stream().filter(OddEvenMarketIds::isEven).findFirst().get(); } public static boolean isEven(OutcomeOdds outcome) { @@ -33,4 +33,8 @@ public static boolean isEven(OutcomeOdds outcome) { public static boolean isEven(OutcomeProbabilities outcome) { return outcome.getId().equals(EVEN_OUTCOME_ID); } + + public static String anyOddEvenOutcomeId() { + return pickOneRandomlyFrom(ODD_OUTCOME_ID, EVEN_OUTCOME_ID); + } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/OneXtwoMarketIds.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/OneXtwoMarketIds.java new file mode 100644 index 00000000..fce9cb4b --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/marketids/OneXtwoMarketIds.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.conn.marketids; + +public final class OneXtwoMarketIds { + + public static final int ONE_X_TWO_MARKET_ID = 1; + + public static final String COMPETITOR_1_OUTCOME_ID = "1"; + + public static final String DRAW_OUTCOME_ID = "2"; + + public static final String COMPETITOR_2_OUTCOME_ID = "3"; + + private OneXtwoMarketIds() {} +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/domain/language/LanguagesBestEffortTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/domain/language/LanguagesBestEffortTest.java new file mode 100644 index 00000000..0311beb3 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/domain/language/LanguagesBestEffortTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.domain.language; + +import static java.util.Arrays.asList; +import static java.util.Locale.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatException; + +import lombok.val; +import org.junit.jupiter.api.Test; + +class LanguagesBestEffortTest { + + @Test + void createsFromList() { + val languages = asList(ENGLISH, GERMAN, FRENCH, JAPANESE); + + val actual = new Languages.BestEffort(languages); + + assertThat(actual.getLanguages()).isEqualTo(languages); + } + + @Test + void createsWithSingleLanguage() { + val aLanguage = UK; + + val actual = new Languages.BestEffort(aLanguage); + + assertThat(actual.getLanguages()).containsOnly(aLanguage); + } + + @Test + void createsFromVarargs() { + val actual = new Languages.BestEffort(GERMAN, FRENCH, JAPANESE); + + assertThat(actual.getLanguages()).containsExactly(GERMAN, FRENCH, JAPANESE); + } + + @Test + void languageListIsImmutable() { + val actual = new Languages.BestEffort(GERMAN, FRENCH, JAPANESE); + + assertThatException() + .isThrownBy(() -> actual.getLanguages().add(ENGLISH)) + .isInstanceOf(UnsupportedOperationException.class); + + assertThatException() + .isThrownBy(() -> actual.getLanguages().remove(FRENCH)) + .isInstanceOf(UnsupportedOperationException.class); + + assertThatException() + .isThrownBy(() -> actual.getLanguages().removeAll(asList(FRENCH))) + .isInstanceOf(UnsupportedOperationException.class); + + assertThatException() + .isThrownBy(() -> actual.getLanguages().addAll(asList(ENGLISH))) + .isInstanceOf(UnsupportedOperationException.class); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/BookmakerDetailsDataProvider.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/BookmakerDetailsDataProvider.java new file mode 100644 index 00000000..8a46d6a3 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/BookmakerDetailsDataProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.impl; + +import static com.sportradar.unifiedodds.sdk.caching.markets.DataProviderAnswers.withGetDataThrowingByDefault; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import com.sportradar.uf.sportsapi.datamodel.BookmakerDetails; +import com.sportradar.unifiedodds.sdk.exceptions.internal.DataProviderException; +import com.sportradar.utils.domain.names.LanguageHolder; +import org.apache.hc.core5.http.Header; + +public class BookmakerDetailsDataProvider { + + public static DataProvider providing( + LanguageHolder language, + BookmakerDetails bookmakerDetails + ) throws DataProviderException { + DataProvider dataProvider = mock( + DataProvider.class, + withGetDataThrowingByDefault() + ); + doReturn(new DataWrapper<>(bookmakerDetails, new Header[] {})) + .when(dataProvider) + .getDataWithAdditionalInfo(language.get()); + return dataProvider; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumerTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumerTest.java index 0492c48c..e824fbbf 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumerTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/ChannelMessageConsumerTest.java @@ -1,21 +1,20 @@ package com.sportradar.unifiedodds.sdk.impl; import static com.sportradar.unifiedodds.sdk.impl.Constants.ODDS_CHANGE_KEY; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.*; -import com.google.inject.Guice; -import com.google.inject.Injector; +import com.google.inject.*; +import com.google.inject.name.Names; import com.google.inject.util.Modules; +import com.sportradar.unifiedodds.sdk.SdkInternalConfiguration; import com.sportradar.unifiedodds.sdk.di.MockedMasterModule; import com.sportradar.unifiedodds.sdk.di.TestingModule; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.*; +import javax.xml.bind.JAXBContext; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -23,15 +22,26 @@ @SuppressWarnings({ "MagicNumber", "VisibilityModifier" }) public class ChannelMessageConsumerTest { + private static final String ROUTING_KEY = "routing_key"; + final Injector injector = Guice.createInjector( Modules.override(new MockedMasterModule()).with(new TestingModule()) ); ChannelMessageConsumer chanMsgConsumer; + private JAXBContext messagingJaxbContext; @Before public void setup() { - chanMsgConsumer = injector.getInstance(ChannelMessageConsumer.class); + messagingJaxbContext = + spy(injector.getInstance(Key.get(JAXBContext.class, Names.named("MessageJAXBContext")))); + chanMsgConsumer = + new ChannelMessageConsumerImpl( + injector.getInstance(RoutingKeyParser.class), + injector.getInstance(SdkInternalConfiguration.class), + injector.getInstance(SdkProducerManager.class), + messagingJaxbContext + ); } @Test @@ -109,7 +119,7 @@ public void invokesOnMsgReceivedForUnparsableRoutingKey() throws Exception { byte[] data = oddsChangeBytes(); //Execute - chanMsgConsumer.onMessageReceived("routing_key", data, null, 0L); + chanMsgConsumer.onMessageReceived(ROUTING_KEY, data, null, 0L); //Verify Mockito @@ -126,7 +136,7 @@ public void deserializationFailedEvent() { byte[] data = new byte[] { 1, 2, 3, 4 }; //this can't be deserialized //Execute - chanMsgConsumer.onMessageReceived("routing_key", data, null, 0L); + chanMsgConsumer.onMessageReceived(ROUTING_KEY, data, null, 0L); //Verify Mockito.verify(msgConsumer).onMessageDeserializationFailed(Mockito.eq(data), Mockito.any()); @@ -157,7 +167,7 @@ public void testUnmarshallerThreadSafe() throws Exception { } executor.invokeAll(callableTasks); - finished.await(); + finished.await(30, TimeUnit.SECONDS); executor.shutdownNow(); @@ -174,11 +184,26 @@ public void getsConsumerDescription() { byte[] data = new byte[] { 1, 2, 3, 4 }; //this can't be deserialized - chanMsgConsumer.onMessageReceived("routing_key", data, null, 0L); + chanMsgConsumer.onMessageReceived(ROUTING_KEY, data, null, 0L); Mockito.verify(msgConsumer).onMessageDeserializationFailed(Mockito.eq(data), Mockito.any()); } + @Test + public void unmarshallerCleanedUpOnClose() throws Exception { + MessageConsumer msgConsumer = Mockito.mock(MessageConsumer.class); + chanMsgConsumer.open(msgConsumer); + + byte[] data = new byte[] { 1, 2, 3, 4 }; //this can't be deserialized + chanMsgConsumer.onMessageReceived(ROUTING_KEY, data, null, 0L); + + chanMsgConsumer.close(); + + chanMsgConsumer.onMessageReceived(ROUTING_KEY, data, null, 0L); + + verify(messagingJaxbContext, times(2)).createUnmarshaller(); + } + private byte[] oddsChangeBytes() throws Exception { return Files.readAllBytes( Paths.get(this.getClass().getClassLoader().getResource(Constants.ODDS_CHANGE_MSG_URI).toURI()) diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpDataFetcherIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpDataFetcherIT.java index c68d2945..b438e693 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpDataFetcherIT.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpDataFetcherIT.java @@ -29,9 +29,7 @@ public abstract class HttpDataFetcherIT { private final String anyPath = "/some/path"; private final int anyErrorResponseCode = 404; - private final HttpResponseHandler httpResponseHandler = new HttpResponseHandler( - new DeserializerImpl(JAXBContext.newInstance("com.sportradar.uf.sportsapi.datamodel")) - ); + private final HttpResponseHandler httpResponseHandler = new HttpResponseHandler(); private final SportsApiXmlResponseProvider xmlResponseProvider = new SportsApiXmlResponseProvider(); private HttpDataFetcher httpFetcher = createHttpDataFetcher( mock(SdkInternalConfiguration.class), diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpDataFetcherLoggersIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpDataFetcherLoggersIT.java index 57c5fda2..8087dce6 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpDataFetcherLoggersIT.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpDataFetcherLoggersIT.java @@ -27,9 +27,7 @@ public class HttpDataFetcherLoggersIT { public final WireMockRule wireMockRule = new WireMockRule(); private final CloseableHttpClient httpClient = HttpClientBuilder.create().build(); - private final HttpResponseHandler httpResponseHandler = new HttpResponseHandler( - new DeserializerImpl(JAXBContext.newInstance("com.sportradar.uf.sportsapi.datamodel")) - ); + private final HttpResponseHandler httpResponseHandler = new HttpResponseHandler(); private final SportsApiXmlResponseProvider xmlResponseProvider = new SportsApiXmlResponseProvider(); public HttpDataFetcherLoggersIT() throws JAXBException {} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpResponseHandlerTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpResponseHandlerTest.java index 2f6a41f2..4c2552cc 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpResponseHandlerTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/HttpResponseHandlerTest.java @@ -26,13 +26,11 @@ public class HttpResponseHandlerTest { private static final String ANY_RESPONSE_BODY = "any"; private static final String EMPTY_RESPONSE_BODY = ""; - private final Deserializer deserializer = mock(Deserializer.class); - private final HttpResponseHandler responseHandler = new HttpResponseHandler(deserializer); + private final HttpResponseHandler responseHandler = new HttpResponseHandler(); private final CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class); private final String path = "/any/path"; private final int successfulResponseCode = 200; - private final String noMessageErrorString = "no message"; private final int failedResponseCode = 404; private final String anySuccessfulResponseMessage = "successful response"; private final String whoAmIPath = "/whoami.xml"; @@ -61,8 +59,7 @@ public void shouldReturnHttpWhenRequestHasAcceptedHttpStatus() } @Test - public void shouldReturnHttpDataForFailedWhoAmIRequestWithForbiddenStatusCode() - throws IOException, ParseException, CommunicationException { + public void shouldReturnHttpDataForFailedWhoAmIRequestWithForbiddenStatusCode() throws Exception { final int forbiddenStatus = 403; String forbiddenResponseString = "forbidden"; setupHttpResponseAndHttpEntity(forbiddenStatus, forbiddenResponseString); @@ -74,12 +71,12 @@ public void shouldReturnHttpDataForFailedWhoAmIRequestWithForbiddenStatusCode() } @Test - public void shouldThrowExceptionWhenWhoAmIRequestFailsAndNotForbiddenStatus() - throws IOException, DeserializationException { + public void shouldThrowExceptionWhenWhoAmIRequestFailsAndNotForbiddenStatus() throws Exception { String anyFailedResponseMessage = "failed response"; - setupHttpResponseAndHttpEntity(failedResponseCode, anyFailedResponseMessage); - - when(deserializer.deserialize(any())).thenReturn(createResponseWithMessage(anyFailedResponseMessage)); + setupHttpResponseAndHttpEntity( + failedResponseCode, + "" + anyFailedResponseMessage + "" + ); String expectedExceptionCauseMessage = createExpectedExceptionMessage( failedResponseCode, @@ -92,11 +89,12 @@ public void shouldThrowExceptionWhenWhoAmIRequestFailsAndNotForbiddenStatus() @Test public void failingDueToEmptyResponseContainsExplanatoryMessage() throws IOException { + String noMessageErrorFromHttpResponseHandler = "no message"; setupHttpResponseAndHttpEntity(successfulResponseCode, EMPTY_RESPONSE_BODY); String expectedExceptionCauseMessage = createExpectedExceptionMessage( successfulResponseCode, - noMessageErrorString + noMessageErrorFromHttpResponseHandler ); assertThatThrownBy(() -> responseHandler.extractHttpDataFromHttpResponse(httpResponse, path)) @@ -126,15 +124,16 @@ public void failingDueToEmptyResponsePreservesUrl() throws IOException { } @Test - public void shouldThrowExceptionForFailedResponse() throws DeserializationException, IOException { - String someFailedResponseString = "failed response"; - setupHttpResponseAndHttpEntity(failedResponseCode, someFailedResponseString); - - when(deserializer.deserialize(any())).thenReturn(createResponseWithMessage(someFailedResponseString)); + public void shouldThrowExceptionForFailedResponse() throws Exception { + String errorMessage = "failed response"; + setupHttpResponseAndHttpEntity( + failedResponseCode, + "" + errorMessage + "" + ); String expectedExceptionCauseMessage = createExpectedExceptionMessage( failedResponseCode, - someFailedResponseString + errorMessage ); assertThatThrownBy(() -> responseHandler.extractHttpDataFromHttpResponse(httpResponse, path)) @@ -142,16 +141,14 @@ public void shouldThrowExceptionForFailedResponse() throws DeserializationExcept } @Test - public void failingDueToUnexpectedHttpCodeCarriesExplanatoryMessage() - throws IOException, DeserializationException { + public void failingDueToUnexpectedHttpCodeCarriesExplanatoryMessage() throws Exception { String emptyResponseString = ""; + String noMessageErrorFromMessageAndActionExtractor = "No specific message"; setupHttpResponseAndHttpEntity(failedResponseCode, emptyResponseString); - when(deserializer.deserialize(any())).thenReturn(createResponseWithMessage(emptyResponseString)); - String expectedExceptionCauseMessage = createExpectedExceptionMessage( failedResponseCode, - noMessageErrorString + noMessageErrorFromMessageAndActionExtractor ); assertThatThrownBy(() -> responseHandler.extractHttpDataFromHttpResponse(httpResponse, path)) @@ -159,10 +156,8 @@ public void failingDueToUnexpectedHttpCodeCarriesExplanatoryMessage() } @Test - public void failingDueToUnexpectedHttpCodePreservesStatusCode() - throws IOException, DeserializationException { + public void failingDueToUnexpectedHttpCodePreservesStatusCode() throws Exception { setupHttpResponseAndHttpEntity(failedResponseCode, ANY_RESPONSE_BODY); - when(deserializer.deserialize(any())).thenReturn(createResponseWithMessage(ANY_RESPONSE_BODY)); val exception = catchThrowableOfType( () -> responseHandler.extractHttpDataFromHttpResponse(httpResponse, path), @@ -173,9 +168,8 @@ public void failingDueToUnexpectedHttpCodePreservesStatusCode() } @Test - public void failingDueToUnexpectedHttpCodePreservesUrl() throws IOException, DeserializationException { + public void failingDueToUnexpectedHttpCodePreservesUrl() throws Exception { setupHttpResponseAndHttpEntity(failedResponseCode, ANY_RESPONSE_BODY); - when(deserializer.deserialize(any())).thenReturn(createResponseWithMessage(ANY_RESPONSE_BODY)); val exception = catchThrowableOfType( () -> responseHandler.extractHttpDataFromHttpResponse(httpResponse, path), diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/MarketDescriptionDataProviders.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/MarketDescriptionDataProviders.java new file mode 100644 index 00000000..a63d2417 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/MarketDescriptionDataProviders.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.impl; + +import static com.sportradar.unifiedodds.sdk.caching.markets.DataProviderAnswers.withGetDataThrowingByDefault; +import static org.mockito.Mockito.*; + +import com.sportradar.uf.sportsapi.datamodel.DescMarket; +import com.sportradar.uf.sportsapi.datamodel.MarketDescriptions; +import com.sportradar.unifiedodds.sdk.conn.Identifiable; +import com.sportradar.unifiedodds.sdk.exceptions.internal.DataProviderException; +import com.sportradar.utils.domain.names.LanguageHolder; + +public final class MarketDescriptionDataProviders { + + private MarketDescriptionDataProviders() {} + + public static DataProvider providingList( + LanguageHolder language, + DescMarket marketDescription + ) throws DataProviderException { + DataProvider dataProvider = mock( + DataProvider.class, + withGetDataThrowingByDefault() + ); + doReturn(getMarketDescriptions(marketDescription)).when(dataProvider).getData(language.get()); + return dataProvider; + } + + public static DataProvider providingList( + LanguageHolder languageA, + DescMarket descriptionA, + LanguageHolder languageB, + DescMarket descriptionB + ) throws DataProviderException { + DataProvider dataProvider = mock( + DataProvider.class, + withGetDataThrowingByDefault() + ); + doReturn(getMarketDescriptions(descriptionA)).when(dataProvider).getData(languageA.get()); + doReturn(getMarketDescriptions(descriptionB)).when(dataProvider).getData(languageB.get()); + return dataProvider; + } + + public static DataProvider providing( + LanguageHolder language, + Identifiable variant, + DescMarket market + ) throws DataProviderException { + DataProvider dataProvider = mock( + DataProvider.class, + withGetDataThrowingByDefault() + ); + doReturn(getMarketDescriptions(market)) + .when(dataProvider) + .getData(language.get(), market.getId() + "", variant.id()); + return dataProvider; + } + + public static DataProvider providing( + LanguageHolder language, + int marketId, + Identifiable variant, + MarketDescriptions descriptions + ) throws DataProviderException { + DataProvider dataProvider = mock( + DataProvider.class, + withGetDataThrowingByDefault() + ); + doReturn(descriptions).when(dataProvider).getData(language.get(), marketId + "", variant.id()); + return dataProvider; + } + + public static DataProvider providing( + LanguageHolder languageA, + Identifiable variantA, + DescMarket marketA, + LanguageHolder languageB, + Identifiable variantB, + DescMarket marketB + ) throws DataProviderException { + DataProvider dataProvider = mock( + DataProvider.class + ); + when(dataProvider.getData(languageA.get(), marketA.getId() + "", variantA.id())) + .thenReturn(getMarketDescriptions(marketA)); + + when(dataProvider.getData(languageB.get(), marketB.getId() + "", variantB.id())) + .thenReturn(getMarketDescriptions(marketB)); + return dataProvider; + } + + public static DataProvider providing( + LanguageHolder languageA, + String variantIdA, + DescMarket marketA, + LanguageHolder languageB, + String variantIdB, + DescMarket marketB + ) throws DataProviderException { + DataProvider dataProvider = mock( + DataProvider.class + ); + when(dataProvider.getData(languageA.get(), marketA.getId() + "", variantIdA)) + .thenReturn(getMarketDescriptions(marketA)); + + when(dataProvider.getData(languageB.get(), marketB.getId() + "", variantIdB)) + .thenReturn(getMarketDescriptions(marketB)); + return dataProvider; + } + + private static MarketDescriptions getMarketDescriptions(DescMarket marketDescription) { + MarketDescriptions descriptions = new MarketDescriptions(); + descriptions.getMarket().add(marketDescription); + return descriptions; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/VariantDescriptionDataProviders.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/VariantDescriptionDataProviders.java new file mode 100644 index 00000000..6f3250f2 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/VariantDescriptionDataProviders.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.impl; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.sportradar.uf.sportsapi.datamodel.DescVariant; +import com.sportradar.uf.sportsapi.datamodel.VariantDescriptions; +import com.sportradar.unifiedodds.sdk.exceptions.internal.DataProviderException; +import com.sportradar.utils.domain.names.LanguageHolder; +import lombok.val; + +public final class VariantDescriptionDataProviders { + + public static DataProvider providingList( + LanguageHolder language, + DescVariant variantDescription + ) throws DataProviderException { + DataProvider dataProvider = mock(DataProvider.class); + when(dataProvider.getData(language.get())).thenReturn(getVariantDescriptions(variantDescription)); + return dataProvider; + } + + private static VariantDescriptions getVariantDescriptions(DescVariant variant) { + val descriptions = new VariantDescriptions(); + descriptions.getVariant().add(variant); + return descriptions; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/apireaders/HttpHelperIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/apireaders/HttpHelperIT.java index e0a19f3c..131a29f5 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/apireaders/HttpHelperIT.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/apireaders/HttpHelperIT.java @@ -42,7 +42,7 @@ public class HttpHelperIT { Key.get(Deserializer.class, Names.named("SportsApiJaxbDeserializer")) ); private final UserAgentProvider userAgent = mock(UserAgentProvider.class); - private final MessageAndActionExtractor messageExtractor = new MessageAndActionExtractor(apiDeserializer); + private final MessageAndActionExtractor messageExtractor = new MessageAndActionExtractor(); private final HttpHelper httpHelper = new HttpHelper(config, httpClient, messageExtractor, userAgent); private final String anyPath = "/path/resource"; diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/apireaders/MessageAndActionExtractorTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/apireaders/MessageAndActionExtractorTest.java index d39eff3a..4264359a 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/apireaders/MessageAndActionExtractorTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/apireaders/MessageAndActionExtractorTest.java @@ -4,113 +4,99 @@ package com.sportradar.unifiedodds.sdk.impl.apireaders; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import com.sportradar.uf.sportsapi.datamodel.APIPageNotFound; +import com.google.common.base.Charsets; +import com.sportradar.uf.custombet.datamodel.CapiResponse; import com.sportradar.uf.sportsapi.datamodel.Response; -import com.sportradar.unifiedodds.sdk.exceptions.internal.DeserializationException; -import com.sportradar.unifiedodds.sdk.impl.Deserializer; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.InputStream; -import lombok.val; -import org.junit.Test; +import javax.xml.bind.JAXB; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class MessageAndActionExtractorTest { - private final Deserializer apiDeserializer = mock(Deserializer.class); + private final MessageAndActionExtractor messageExtractor = new MessageAndActionExtractor(); - private final MessageAndActionExtractor messageExtractor = new MessageAndActionExtractor(apiDeserializer); - - @Test - public void extractsMessageFromResponse() throws DeserializationException { - val message = "some message"; - when(apiDeserializer.deserialize(any())).thenReturn(responseWithMessage(message)); - - String parsedMessage = messageExtractor.parse(mock(InputStream.class)); + @ParameterizedTest + @ValueSource( + strings = { + "No data for the event: sr:match:123:prematch", + "No data for the event: sr:match:123:prematch, bookmakerId: 16281", + } + ) + public void extractsMessageFromCustomBetResponse(String message) { + String parsedMessage = messageExtractor.parse(customBetResponse(message)); assertThat(parsedMessage).isEqualTo(message); } - @Test - public void extractsActionAsMessageFromResponse() throws DeserializationException { - val action = "some action"; - when(apiDeserializer.deserialize(any())).thenReturn(responseWithAction(action)); - - String parsedMessage = messageExtractor.parse(mock(InputStream.class)); + @ParameterizedTest + @ValueSource(strings = { "Content not found: null", "some other message" }) + public void extractsMessageFromSportsApiResponse(String message) { + String parsedMessage = messageExtractor.parse(sportsApiResponse(message)); - assertThat(parsedMessage).isEqualTo(action); + assertThat(parsedMessage).isEqualTo(message); } - @Test - public void concatenateMessageAndActionFromResponseIfBothAvailable() throws DeserializationException { - when(apiDeserializer.deserialize(any())).thenReturn(responseWithMessageAndAction("msg", "action")); - - String parsedMessage = messageExtractor.parse(mock(InputStream.class)); + @ParameterizedTest + @ValueSource(strings = { "missing info", "no such player" }) + public void concatenateMessageAndActionFromResponseIfBothAvailable(String action) { + String parsedMessage = messageExtractor.parse(sportsApiResponse("msg for " + action, action)); - assertThat(parsedMessage).isEqualTo("msg, action"); + assertThat(parsedMessage).isEqualTo("msg for " + action + ", " + action); } @Test - public void extractsEmptyMessageIfNoMessageReceived() throws DeserializationException { - when(apiDeserializer.deserialize(any())).thenReturn(new Response()); - - String parsedMessage = messageExtractor.parse(mock(InputStream.class)); + public void extractsEmptyMessageIfNoMessageReceivedForSportsApi() { + String parsedMessage = messageExtractor.parse(sportsApiResponse(null)); assertThat(parsedMessage).isEmpty(); } @Test - public void parsesMessageWhenPageIsNotFound() throws DeserializationException { - val message = "some message"; - when(apiDeserializer.deserialize(any())).thenReturn(pageNotFoundWithMessage(message)); - - String parsedMessage = messageExtractor.parse(mock(InputStream.class)); - - assertThat(parsedMessage).isEqualTo(message); - } - - @Test - public void onParsingUnexpectedClassReturnsItsNameAsPartOfMessage() throws DeserializationException { - Object unexpectedParsedType = new Object(); - when(apiDeserializer.deserialize(any())).thenReturn(unexpectedParsedType); + public void extractsEmptyMessageIfNoMessageReceivedForCustomBetApi() { + String parsedMessage = messageExtractor.parse(customBetResponse(null)); - String parsedMessage = messageExtractor.parse(mock(InputStream.class)); - - assertThat(parsedMessage).contains("Unknown").contains("Object"); + assertThat(parsedMessage).isEmpty(); } @Test - public void failedParsingIsIndicatedWithMessage() throws DeserializationException { - when(apiDeserializer.deserialize(any())).thenThrow(DeserializationException.class); - - String parsedMessage = messageExtractor.parse(mock(InputStream.class)); + public void failedParsingIsIndicatedWithMessage() { + String parsedMessage = messageExtractor.parse(jsonResponse()); assertThat(parsedMessage).isEqualTo("No specific message"); } - private APIPageNotFound pageNotFoundWithMessage(String message) { - APIPageNotFound response = new APIPageNotFound(); + private InputStream customBetResponse(String message) { + CapiResponse response = new CapiResponse(); response.setMessage(message); - return response; + return asInputStream(response); } - private Response responseWithMessage(String message) { + private InputStream sportsApiResponse(String message) { Response response = new Response(); response.setMessage(message); - return response; + return asInputStream(response); } - private Response responseWithAction(String action) { + private InputStream sportsApiResponse(String message, String action) { Response response = new Response(); + response.setMessage(message); response.setAction(action); - return response; + return asInputStream(response); } - private Response responseWithMessageAndAction(String message, String action) { - Response response = new Response(); - response.setMessage(message); - response.setAction(action); - return response; + private static ByteArrayInputStream asInputStream(Object response) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + JAXB.marshal(response, output); + return new ByteArrayInputStream(output.toByteArray()); + } + + private InputStream jsonResponse() { + String response = "{\"a\": 1111}"; + return new ByteArrayInputStream(response.getBytes(Charsets.UTF_8)); } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentAssert.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentAssert.java new file mode 100644 index 00000000..b88bf62e --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentAssert.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ + +package com.sportradar.unifiedodds.sdk.impl.entities; + +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.entities.BasicTournament; +import com.sportradar.unifiedodds.sdk.exceptions.ObjectNotFoundException; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assertions; + +class BasicTournamentAssert extends AbstractAssert { + + private BasicTournamentAssert(BasicTournament tournament) { + super(tournament, BasicTournamentAssert.class); + } + + public static BasicTournamentAssert assertThat(BasicTournament tournament) { + return new BasicTournamentAssert(tournament); + } + + public BasicTournamentAssert doesNotHaveStartTimeTbd(ExceptionHandlingStrategy errorHandling) { + if (errorHandling == ExceptionHandlingStrategy.Throw) { + Assertions + .assertThatExceptionOfType(ObjectNotFoundException.class) + .isThrownBy(actual::isStartTimeTbd); + } else { + Assertions.assertThat(actual.isStartTimeTbd()).isNull(); + } + return this; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentImplTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentImplTest.java index c0ab62eb..6e7ae3af 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentImplTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/BasicTournamentImplTest.java @@ -8,15 +8,18 @@ import static java.util.Arrays.asList; import static java.util.Locale.ENGLISH; import static java.util.Locale.FRENCH; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; -import static org.mockito.Mockito.times; +import com.google.common.collect.ImmutableList; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; import com.sportradar.unifiedodds.sdk.SportEntityFactory; import com.sportradar.unifiedodds.sdk.caching.DataRouterManager; +import com.sportradar.unifiedodds.sdk.caching.SportEventCache; +import com.sportradar.unifiedodds.sdk.caching.TournamentCi; import com.sportradar.unifiedodds.sdk.caching.impl.SportEventCacheImpl; import com.sportradar.unifiedodds.sdk.entities.BasicTournament; import com.sportradar.unifiedodds.sdk.entities.Competition; @@ -25,7 +28,13 @@ import com.sportradar.utils.Urn; import java.util.List; import java.util.Locale; -import org.junit.Test; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; public class BasicTournamentImplTest { @@ -127,4 +136,66 @@ public void noFurtherLanguagesShouldBeAttemptedAfterFailingOneWhenGettingSchedul .hasMessageContaining("getSchedule failure"); verify(dataRouterManager, times(1)).requestEventsFor(any(), any(Urn.class)); } + + @Nested + class IsStartTimeTbd { + + private SportEventCache sportEventCache; + + @BeforeEach + void stub() { + sportEventCache = mock(SportEventCache.class); + } + + @ParameterizedTest + @EnumSource(ExceptionHandlingStrategy.class) + void whenNoTournamentCiInCache(ExceptionHandlingStrategy strategy) { + BasicTournamentAssert.assertThat(tournament(strategy)).doesNotHaveStartTimeTbd(strategy); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void returnsValueWhenTournamentFoundInCache(boolean isStartTimeTbd) throws Exception { + TournamentCi tournament = tournamentCi(isStartTimeTbd); + when(sportEventCache.getEventCacheItem(tournamentUrn)).thenReturn(tournament); + + assertThat(tournament(tournamentUrn).isStartTimeTbd()).isEqualTo(isStartTimeTbd); + } + + @Test + void returnsNullWhenTournamentFoundInCacheBuStartTimeTbdEmpty() throws Exception { + TournamentCi tournament = tournamentCi(null); + when(sportEventCache.getEventCacheItem(tournamentUrn)).thenReturn(tournament); + + assertThat(tournament(tournamentUrn).isStartTimeTbd()).isNull(); + } + + private TournamentCi tournamentCi(Boolean isStartTimeTbd) { + TournamentCi tournament = mock(TournamentCi.class); + when(tournament.isStartTimeTbd()).thenReturn(Optional.ofNullable(isStartTimeTbd)); + return tournament; + } + + private BasicTournamentImpl tournament(ExceptionHandlingStrategy exceptionHandlingStrategy) { + return new BasicTournamentImpl( + tournamentUrn, + urnForAnySport(), + ImmutableList.of(), + sportEventCache, + anyFactory, + exceptionHandlingStrategy + ); + } + + private BasicTournamentImpl tournament(Urn urn) { + return new BasicTournamentImpl( + urn, + urnForAnySport(), + ImmutableList.of(), + sportEventCache, + anyFactory, + ExceptionHandlingStrategy.Throw + ); + } + } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawAssert.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawAssert.java new file mode 100644 index 00000000..4502180a --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawAssert.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ + +package com.sportradar.unifiedodds.sdk.impl.entities; + +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.entities.Draw; +import com.sportradar.unifiedodds.sdk.exceptions.ObjectNotFoundException; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assertions; + +class DrawAssert extends AbstractAssert { + + private DrawAssert(Draw draw) { + super(draw, DrawAssert.class); + } + + public static DrawAssert assertThat(Draw draw) { + return new DrawAssert(draw); + } + + public DrawAssert doesNotHaveStartTimeTbd(ExceptionHandlingStrategy errorHandling) { + if (errorHandling == ExceptionHandlingStrategy.Throw) { + Assertions + .assertThatExceptionOfType(ObjectNotFoundException.class) + .isThrownBy(actual::isStartTimeTbd); + } else { + Assertions.assertThat(actual.isStartTimeTbd()).isNull(); + } + return this; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawImplTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawImplTest.java new file mode 100644 index 00000000..85bdf44b --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/DrawImplTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ + +package com.sportradar.unifiedodds.sdk.impl.entities; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +import com.google.common.collect.ImmutableList; +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.SportEntityFactory; +import com.sportradar.unifiedodds.sdk.caching.DrawCi; +import com.sportradar.unifiedodds.sdk.caching.SportEventCache; +import com.sportradar.utils.Urn; +import com.sportradar.utils.Urns; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + +class DrawImplTest { + + private SportEventCache sportEventCache; + private SportEntityFactory sportEntityFactory; + + @BeforeEach + void stub() { + sportEventCache = mock(SportEventCache.class); + sportEntityFactory = mock(SportEntityFactory.class); + } + + private DrawImpl draw(ExceptionHandlingStrategy exceptionHandlingStrategy) { + return new DrawImpl( + Urns.SportEvents.any(), + Urns.Sports.urnForAnySport(), + ImmutableList.of(), + sportEventCache, + sportEntityFactory, + exceptionHandlingStrategy + ); + } + + private DrawImpl draw(Urn id) { + return new DrawImpl( + id, + Urns.Sports.urnForAnySport(), + ImmutableList.of(), + sportEventCache, + sportEntityFactory, + ExceptionHandlingStrategy.Throw + ); + } + + @Nested + class IsStartTimeTbd { + + @ParameterizedTest + @EnumSource(ExceptionHandlingStrategy.class) + void whenNoDrawCiInCache(ExceptionHandlingStrategy strategy) { + DrawAssert.assertThat(draw(strategy)).doesNotHaveStartTimeTbd(strategy); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void returnsValueWhenDrawFoundInCache(boolean isStartTimeTbd) throws Exception { + Urn id = Urns.SportEvents.getForAnyMatch(); + DrawCi draw = drawCi(isStartTimeTbd); + when(sportEventCache.getEventCacheItem(id)).thenReturn(draw); + + assertThat(draw(id).isStartTimeTbd()).isEqualTo(isStartTimeTbd); + } + + @Test + void returnsNullWhenDrawFoundInCacheBuStartTimeTbdEmpty() throws Exception { + Urn id = Urns.SportEvents.getForAnyMatch(); + DrawCi draw = drawCi(null); + when(sportEventCache.getEventCacheItem(id)).thenReturn(draw); + + assertThat(draw(id).isStartTimeTbd()).isNull(); + } + + private DrawCi drawCi(Boolean isStartTimeTbd) { + DrawCi draw = mock(DrawCi.class); + when(draw.isStartTimeTbd()).thenReturn(Optional.ofNullable(isStartTimeTbd)); + return draw; + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryAssert.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryAssert.java new file mode 100644 index 00000000..9ccef5d0 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryAssert.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ + +package com.sportradar.unifiedodds.sdk.impl.entities; + +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.entities.Lottery; +import com.sportradar.unifiedodds.sdk.exceptions.ObjectNotFoundException; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assertions; + +class LotteryAssert extends AbstractAssert { + + private LotteryAssert(Lottery lottery) { + super(lottery, LotteryAssert.class); + } + + public static LotteryAssert assertThat(Lottery lottery) { + return new LotteryAssert(lottery); + } + + public LotteryAssert doesNotHaveStartTimeTbd(ExceptionHandlingStrategy errorHandling) { + if (errorHandling == ExceptionHandlingStrategy.Throw) { + Assertions + .assertThatExceptionOfType(ObjectNotFoundException.class) + .isThrownBy(actual::isStartTimeTbd); + } else { + Assertions.assertThat(actual.isStartTimeTbd()).isNull(); + } + return this; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryImplTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryImplTest.java new file mode 100644 index 00000000..51caeefb --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/LotteryImplTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ + +package com.sportradar.unifiedodds.sdk.impl.entities; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableList; +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.SportEntityFactory; +import com.sportradar.unifiedodds.sdk.caching.LotteryCi; +import com.sportradar.unifiedodds.sdk.caching.SportEventCache; +import com.sportradar.utils.Urn; +import com.sportradar.utils.Urns; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + +class LotteryImplTest { + + private SportEventCache sportEventCache; + private SportEntityFactory sportEntityFactory; + + @BeforeEach + void stub() { + sportEventCache = mock(SportEventCache.class); + sportEntityFactory = mock(SportEntityFactory.class); + } + + private LotteryImpl lottery(ExceptionHandlingStrategy exceptionHandlingStrategy) { + return new LotteryImpl( + Urns.SportEvents.any(), + Urns.Sports.urnForAnySport(), + ImmutableList.of(), + sportEventCache, + sportEntityFactory, + exceptionHandlingStrategy + ); + } + + private LotteryImpl lottery(Urn id) { + return new LotteryImpl( + id, + Urns.Sports.urnForAnySport(), + ImmutableList.of(), + sportEventCache, + sportEntityFactory, + ExceptionHandlingStrategy.Throw + ); + } + + @Nested + class IsStartTimeTbd { + + @ParameterizedTest + @EnumSource(ExceptionHandlingStrategy.class) + void whenNoLotteryCiInCache(ExceptionHandlingStrategy strategy) { + LotteryAssert.assertThat(lottery(strategy)).doesNotHaveStartTimeTbd(strategy); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void returnsValueWhenLotteryFoundInCache(boolean isStartTimeTbd) throws Exception { + Urn id = Urns.SportEvents.getForAnyMatch(); + LotteryCi lottery = lotteryCi(isStartTimeTbd); + when(sportEventCache.getEventCacheItem(id)).thenReturn(lottery); + + assertThat(lottery(id).isStartTimeTbd()).isEqualTo(isStartTimeTbd); + } + + @Test + void returnsNullWhenLotteryFoundInCacheBuStartTimeTbdEmpty() throws Exception { + Urn id = Urns.SportEvents.getForAnyMatch(); + LotteryCi lottery = lotteryCi(null); + when(sportEventCache.getEventCacheItem(id)).thenReturn(lottery); + + assertThat(lottery(id).isStartTimeTbd()).isNull(); + } + + private LotteryCi lotteryCi(Boolean isStartTimeTbd) { + LotteryCi lottery = mock(LotteryCi.class); + when(lottery.isStartTimeTbd()) + .thenReturn(Optional.ofNullable(isStartTimeTbd)) + .thenReturn(Optional.empty()); + return lottery; + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/MatchImplTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/MatchImplTest.java index 3095cd4b..a666d5e8 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/MatchImplTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/MatchImplTest.java @@ -8,8 +8,11 @@ import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Throw; import static com.sportradar.unifiedodds.sdk.impl.entities.MatchAssertions.assertThat; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static java.util.Locale.ENGLISH; import static java.util.Locale.FRENCH; +import static java.util.Optional.ofNullable; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; @@ -27,9 +30,11 @@ import com.sportradar.utils.Urn; import com.sportradar.utils.Urns; import java.util.*; +import java.util.function.Supplier; import junitparams.JUnitParamsRunner; import junitparams.Parameters; import lombok.val; +import org.junit.Before; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; @@ -348,4 +353,67 @@ private MatchCi createMatchCiWithTranslatedName(Map translatedNa return matchCi; } } + + @RunWith(JUnitParamsRunner.class) + public static class StartTimeTbd { + + private static final String UNDER_20_EN = "Under 20"; + private static final Urn MATCH_URN = Urns.SportEvents.getForAnyMatch(); + private static final Urn ANY_SPORT_URN = Urns.Sports.urnForAnySport(); + private static final ExceptionHandlingStrategy ANY_EXCEPTION_HANDLING = Throw; + private final SportEntityFactory entityFactory = mock(SportEntityFactory.class); + private final SportEventCache sportEventCache = mock(SportEventCache.class); + private final SportEventStatusFactory statusFactory = mock(SportEventStatusFactory.class); + private final Locale language = ENGLISH; + private MatchImpl match; + + @Before + public void createMatch() { + match = + new MatchImpl( + MATCH_URN, + ANY_SPORT_URN, + sportEventCache, + statusFactory, + entityFactory, + singletonList(language), + ANY_EXCEPTION_HANDLING + ); + } + + @Test + public void startTimeTbdNull() throws Exception { + MatchCi matchCi = matchCi(of(language, UNDER_20_EN), withStartTimeTbd(null)); + when(sportEventCache.getEventCacheItem(MATCH_URN)).thenReturn(matchCi); + + assertThat(match.isStartTimeTbd()).isNull(); + } + + @Test + public void startTimeIsToBeDefined() throws Exception { + MatchCi matchCi = matchCi(of(language, UNDER_20_EN), withStartTimeTbd(true)); + when(sportEventCache.getEventCacheItem(MATCH_URN)).thenReturn(matchCi); + + assertThat(match.isStartTimeTbd()).isTrue(); + } + + @Test + public void startTimeIsNotToBeDefined() throws Exception { + MatchCi matchCi = matchCi(of(language, UNDER_20_EN), withStartTimeTbd(false)); + when(sportEventCache.getEventCacheItem(MATCH_URN)).thenReturn(matchCi); + + assertThat(match.isStartTimeTbd()).isFalse(); + } + + private Supplier withStartTimeTbd(Boolean timestampTbd) { + return () -> timestampTbd; + } + + private MatchCi matchCi(Map translatedName, Supplier timestampTbd) { + val matchCi = mock(MatchCi.class); + when(matchCi.getNames(new ArrayList<>(translatedName.keySet()))).thenReturn(translatedName); + when(matchCi.isStartTimeTbd()).thenReturn(ofNullable(timestampTbd.get())); + return matchCi; + } + } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/StageAssert.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/StageAssert.java new file mode 100644 index 00000000..c52a392f --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/StageAssert.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ + +package com.sportradar.unifiedodds.sdk.impl.entities; + +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.entities.Stage; +import com.sportradar.unifiedodds.sdk.exceptions.ObjectNotFoundException; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assertions; + +class StageAssert extends AbstractAssert { + + private StageAssert(Stage stage) { + super(stage, StageAssert.class); + } + + public static StageAssert assertThat(Stage stage) { + return new StageAssert(stage); + } + + public StageAssert doesNotHaveStartTimeTbd(ExceptionHandlingStrategy errorHandling) { + if (errorHandling == ExceptionHandlingStrategy.Throw) { + Assertions + .assertThatExceptionOfType(ObjectNotFoundException.class) + .isThrownBy(actual::isStartTimeTbd); + } else { + Assertions.assertThat(actual.isStartTimeTbd()).isNull(); + } + return this; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/StageImplTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/StageImplTest.java new file mode 100644 index 00000000..a4465d90 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/StageImplTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.impl.entities; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableList; +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.SportEntityFactory; +import com.sportradar.unifiedodds.sdk.caching.SportEventCache; +import com.sportradar.unifiedodds.sdk.caching.StageCi; +import com.sportradar.unifiedodds.sdk.impl.SportEventStatusFactory; +import com.sportradar.utils.Urn; +import com.sportradar.utils.Urns; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + +class StageImplTest { + + private SportEventCache sportEventCache; + private SportEntityFactory sportEntityFactory; + private SportEventStatusFactory sportEventStatusFactory; + + @BeforeEach + void stub() { + sportEventCache = mock(SportEventCache.class); + sportEntityFactory = mock(SportEntityFactory.class); + sportEventStatusFactory = mock(SportEventStatusFactory.class); + } + + private StageImpl stage(ExceptionHandlingStrategy exceptionHandlingStrategy) { + return new StageImpl( + Urns.SportEvents.urnForAnyStage(), + Urns.Sports.urnForAnySport(), + sportEventCache, + sportEventStatusFactory, + sportEntityFactory, + ImmutableList.of(), + exceptionHandlingStrategy + ); + } + + private StageImpl stage(Urn id) { + return new StageImpl( + id, + Urns.Sports.urnForAnySport(), + sportEventCache, + sportEventStatusFactory, + sportEntityFactory, + ImmutableList.of(), + ExceptionHandlingStrategy.Throw + ); + } + + @Nested + class IsStartTimeTbd { + + @ParameterizedTest + @EnumSource(ExceptionHandlingStrategy.class) + void whenNoStageCiInCache(ExceptionHandlingStrategy strategy) { + StageAssert.assertThat(stage(strategy)).doesNotHaveStartTimeTbd(strategy); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void returnsValueWhenStageFoundInCache(boolean isStartTimeTbd) throws Exception { + Urn id = Urns.SportEvents.urnForAnyStage(); + StageCi stageCi = stageCi(isStartTimeTbd); + when(sportEventCache.getEventCacheItem(id)).thenReturn(stageCi); + + assertThat(stage(id).isStartTimeTbd()).isEqualTo(isStartTimeTbd); + } + + @Test + void returnsNullWhenStageFoundInCacheBuStartTimeTbdEmpty() throws Exception { + Urn id = Urns.SportEvents.urnForAnyStage(); + StageCi stageCi = stageCi(null); + when(sportEventCache.getEventCacheItem(id)).thenReturn(stageCi); + + assertThat(stage(id).isStartTimeTbd()).isNull(); + } + + private StageCi stageCi(Boolean isStartTimeTbd) { + StageCi stage = mock(StageCi.class); + when(stage.isStartTimeTbd()).thenReturn(Optional.ofNullable(isStartTimeTbd)); + return stage; + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentAssert.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentAssert.java new file mode 100644 index 00000000..5e583ab8 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentAssert.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ + +package com.sportradar.unifiedodds.sdk.impl.entities; + +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.entities.Stage; +import com.sportradar.unifiedodds.sdk.entities.Tournament; +import com.sportradar.unifiedodds.sdk.exceptions.ObjectNotFoundException; +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assertions; + +class TournamentAssert extends AbstractAssert { + + private TournamentAssert(Tournament tournament) { + super(tournament, TournamentAssert.class); + } + + public static TournamentAssert assertThat(Tournament tournament) { + return new TournamentAssert(tournament); + } + + public TournamentAssert doesNotHaveStartTimeTbd(ExceptionHandlingStrategy errorHandling) { + if (errorHandling == ExceptionHandlingStrategy.Throw) { + Assertions + .assertThatExceptionOfType(ObjectNotFoundException.class) + .isThrownBy(actual::isStartTimeTbd); + } else { + Assertions.assertThat(actual.isStartTimeTbd()).isNull(); + } + return this; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentImplTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentImplTest.java index 9382c485..8aa94667 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentImplTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/entities/TournamentImplTest.java @@ -8,30 +8,50 @@ import static java.util.Arrays.asList; import static java.util.Locale.ENGLISH; import static java.util.Locale.FRENCH; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.*; +import com.google.common.collect.ImmutableList; +import com.sportradar.unifiedodds.sdk.ConfigureCacheTimeouts; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; import com.sportradar.unifiedodds.sdk.SportEntityFactory; import com.sportradar.unifiedodds.sdk.caching.DataRouterManager; +import com.sportradar.unifiedodds.sdk.caching.SportEventCache; +import com.sportradar.unifiedodds.sdk.caching.StageCi; +import com.sportradar.unifiedodds.sdk.caching.TournamentCi; import com.sportradar.unifiedodds.sdk.caching.impl.SportEventCacheImpl; import com.sportradar.unifiedodds.sdk.entities.Competition; import com.sportradar.unifiedodds.sdk.entities.Tournament; import com.sportradar.unifiedodds.sdk.exceptions.ObjectNotFoundException; import com.sportradar.unifiedodds.sdk.exceptions.internal.CommunicationException; import com.sportradar.utils.Urn; +import com.sportradar.utils.Urns; import java.util.List; import java.util.Locale; -import org.junit.Test; +import java.util.Optional; +import javax.swing.text.html.Option; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; public class TournamentImplTest { - private final SportEntityFactory anyFactory = mock(SportEntityFactory.class); + private SportEntityFactory anyFactory; + private DataRouterManager dataRouterManager; private final Urn tournamentUrn = urnForAnyTournament(); - private final DataRouterManager dataRouterManager = mock(DataRouterManager.class); private final Locale inEnglish = ENGLISH; + @BeforeEach + void stub() { + anyFactory = mock(SportEntityFactory.class); + dataRouterManager = mock(DataRouterManager.class); + } + @Test public void failingToGetScheduledSportEventIdsComposingScheduleShouldReturnNullWhenConfiguredToCatchExceptions() throws CommunicationException { @@ -125,4 +145,70 @@ public void noFurtherLanguagesShouldBeAttemptedAfterFailingOneWhenGettingSchedul .hasMessageContaining("getSchedule failure"); verify(dataRouterManager, times(1)).requestEventsFor(any(), any(Urn.class)); } + + @Nested + class IsStartTimeTbd { + + private SportEventCache sportEventCache; + + @BeforeEach + void createCache() { + sportEventCache = mock(SportEventCache.class); + } + + @ParameterizedTest + @EnumSource(ExceptionHandlingStrategy.class) + void whenNoStageCiInCache(ExceptionHandlingStrategy strategy) { + TournamentAssert.assertThat(tournament(strategy)).doesNotHaveStartTimeTbd(strategy); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void returnsValueWhenStageFoundInCache(boolean isStartTimeTbd) throws Exception { + Urn id = Urns.SportEvents.urnForAnyTournament(); + TournamentCi tournamentCi = tournamentCi(isStartTimeTbd); + when(sportEventCache.getEventCacheItem(id)).thenReturn(tournamentCi); + + assertThat(tournament(id).isStartTimeTbd()).isEqualTo(isStartTimeTbd); + } + + @Test + void returnsNullWhenStageFoundInCacheBuStartTimeTbdEmpty() throws Exception { + Urn id = Urns.SportEvents.urnForAnyTournament(); + TournamentCi tournamentCi = tournamentCi(null); + when(sportEventCache.getEventCacheItem(id)).thenReturn(tournamentCi); + + assertThat(tournament(id).isStartTimeTbd()).isNull(); + } + + private TournamentCi tournamentCi(Boolean isStartTimeTbd) { + TournamentCi tournamentCi = mock(TournamentCi.class); + when(tournamentCi.isStartTimeTbd()) + .thenReturn(Optional.ofNullable(isStartTimeTbd)) + .thenReturn(Optional.empty()); + return tournamentCi; + } + + private TournamentImpl tournament(ExceptionHandlingStrategy exceptionHandlingStrategy) { + return new TournamentImpl( + Urns.SportEvents.urnForAnyTournament(), + Urns.Sports.urnForAnySport(), + ImmutableList.of(), + sportEventCache, + anyFactory, + exceptionHandlingStrategy + ); + } + + private TournamentImpl tournament(Urn id) { + return new TournamentImpl( + id, + Urns.Sports.urnForAnySport(), + ImmutableList.of(), + sportEventCache, + anyFactory, + ExceptionHandlingStrategy.Throw + ); + } + } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderImplTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderImplTest.java index a41638f5..2f097acc 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderImplTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderImplTest.java @@ -3,21 +3,23 @@ */ package com.sportradar.unifiedodds.sdk.impl.markets; -import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategies.anyErrorHandlingStrategy; import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Catch; import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Throw; +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionFactory.namesOf; +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionProviders.subbingOutCaches; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.noMarketDescribingProvider; +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.providing; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; import static com.sportradar.unifiedodds.sdk.impl.markets.NameProviders.usingFactory; -import static com.sportradar.utils.domain.markets.MarketIds.anyMarketId; +import static com.sportradar.utils.domain.names.LanguageHolder.in; import static com.sportradar.utils.domain.names.Languages.anyLanguages; -import static com.sportradar.utils.domain.producers.ProducerIds.anyProducerId; -import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import com.sportradar.uf.sportsapi.datamodel.DescMarket; import com.sportradar.unifiedodds.sdk.SdkInternalConfiguration; import com.sportradar.unifiedodds.sdk.caching.ProfileCache; import com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionProvider; @@ -51,15 +53,12 @@ public void failsToConstructWithoutRequiredArguments() { @Test public void throwsOnMarketNotFoundWhenSdkConfiguredToThrow() throws CacheItemNotFoundException { - val marketDescriptorProvider = mock(MarketDescriptionProvider.class); - when(marketDescriptorProvider.getMarketDescription(anyInt(), anyMap(), anyList(), anyBoolean())) - .thenThrow(CacheItemNotFoundException.class); val nameProvider = usingFactory() - .withMarketDescriptorProvider(marketDescriptorProvider) + .withMarketDescriptorProvider(subbingOutCaches().build()) .withExceptionHandlingStrategy(Throw) .construct(); - assertThatThrownBy(() -> nameProvider.getMarketNames(anyLanguages())) + assertThatThrownBy(() -> nameProvider.getMarketNames(singletonList(Languages.any()))) .isInstanceOf(NameGenerationException.class) .hasMessageContaining("Failed to retrieve market name descriptor"); assertThatThrownBy(() -> nameProvider.getMarketName(Languages.any())) @@ -67,6 +66,27 @@ public void throwsOnMarketNotFoundWhenSdkConfiguredToThrow() throws CacheItemNot .hasMessageContaining("Failed to retrieve market name descriptor"); } + @Test + public void returnsNullOnMarketMissingNameWhenSdkConfiguredToCatch() { + val aLanguage = ENGLISH; + val nameProvider = usingFactory() + .withMarketDescriptorProvider( + providing( + in(aLanguage), + namesOf(nullifyName(oddEvenMarketDescription(aLanguage)), in(aLanguage)) + ) + ) + .withExceptionHandlingStrategy(Catch) + .construct(); + + assertThat(nameProvider.getMarketName(aLanguage)).isNull(); + } + + private DescMarket nullifyName(DescMarket market) { + market.setName(""); + return market; + } + @Test public void swallowsOnMarketNotFoundWhenSdkConfiguredToCatch() { val nameProvider = usingFactory() diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderStatusMessageTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderStatusMessageTest.java index b8ff2498..9e9d6b21 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderStatusMessageTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/markets/NameProviderStatusMessageTest.java @@ -3,8 +3,8 @@ */ package com.sportradar.unifiedodds.sdk.impl.markets; -import static com.sportradar.unifiedodds.sdk.conn.OddEvenMarket.EVEN_OUTCOME_ID; -import static com.sportradar.unifiedodds.sdk.conn.OddEvenMarket.ODD_OUTCOME_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds.EVEN_OUTCOME_ID; +import static com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds.ODD_OUTCOME_ID; import static com.sportradar.unifiedodds.sdk.entities.SportEvents.any; import static com.sportradar.utils.domain.markets.MarketIds.anyMarketId; import static com.sportradar.utils.domain.specifiers.MarketSpecifiers.anySpecifiers; diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketAssert.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketAssert.java index af949d08..37cffa21 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketAssert.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketAssert.java @@ -32,7 +32,7 @@ public MarketAssert hasName(TranslationHolder translation) { return this; } - public MarketAssert nameIsNotBackedByMarketDescriptionForDefaultLanguage( + public MarketAssert getNameForDefault( Locale aLanguage, ExpectationTowardsSdkErrorHandlingStrategy errorHandling ) { @@ -48,7 +48,7 @@ public MarketAssert nameIsNotBackedByMarketDescriptionForDefaultLanguage( return this; } - public MarketAssert nameIsNotBackedByMarketDescriptionForNonDefaultLanguage( + public MarketAssert getNameForGiven( Locale aLanguage, ExpectationTowardsSdkErrorHandlingStrategy errorHandling ) { @@ -65,7 +65,11 @@ public MarketAssert nameIsNotBackedByMarketDescriptionForNonDefaultLanguage( private void resultsInMarketDescriptionNotFoundWhen(Runnable runnable) { assertThatThrownBy(runnable::run) .isInstanceOf(NameGenerationException.class) - .hasMessageContaining("Failed to retrieve market name descriptor"); + .message() + .containsAnyOf( + "Failed to retrieve market name descriptor", + "Retrieved market descriptor does not contain name descriptor in the specified languages" + ); } private void assertThatIsNull(Object result) { diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketDefinitionAssert.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketDefinitionAssert.java index eea1a195..e10014e4 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketDefinitionAssert.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketDefinitionAssert.java @@ -6,8 +6,10 @@ import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketDefinitionAssert.MethodsBackedByMarketDescriptionInScope.ALL_METHODS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketDefinitionAssert.MethodsBackedByMarketDescriptionInScope.METHODS_EXCLUDING_VALID_MAPPINGS; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.sportradar.unifiedodds.sdk.exceptions.NameGenerationException; import com.sportradar.unifiedodds.sdk.exceptions.ObjectNotFoundException; import com.sportradar.unifiedodds.sdk.oddsentities.MarketDefinition; import java.util.Locale; diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplCancelTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplCancelTest.java index bffae332..ec49c08c 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplCancelTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplCancelTest.java @@ -3,11 +3,11 @@ */ package com.sportradar.unifiedodds.sdk.impl.oddsentities.markets; -import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptions.namesOf; +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionFactory.namesOf; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.noMarketDescribingProvider; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.providing; -import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.oddEvenDescription; -import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.Simple.oddEven; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.Simple.oddEvenMarket; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_CATCH_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketAssert.assertThat; @@ -17,14 +17,11 @@ import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketFactories.BuilderStubbingOutSportEventAndCaches.stubbingOutSportEventAndCaches; import static com.sportradar.utils.domain.names.LanguageHolder.in; import static com.sportradar.utils.domain.names.TranslationHolder.of; -import static com.sportradar.utils.domain.names.TranslationHolder.with; import static com.sportradar.utils.domain.producers.ProducerIds.PREMIUM_CRICKET_PRODUCER_ID; import static com.sportradar.utils.domain.producers.ProducerIds.anyProducerId; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; -import com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptions; import com.sportradar.unifiedodds.sdk.entities.SportEvents; import com.sportradar.utils.domain.UniqueObjects; import com.sportradar.utils.domain.names.Languages; @@ -51,7 +48,7 @@ public static class WhenMarketIsNotBackedWithMarketDescription { @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsInDefaultLanguage( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) { val aLanguage = Languages.any(); val marketFactory = stubbingOutSportEventAndCaches() @@ -60,17 +57,18 @@ public void marketNameRetrievalFailsInDefaultLanguage( .with(exceptionHandlingStrategy) .build(); - val market = marketFactory.buildMarketCancel(SportEvents.any(), oddEven(), anyProducerId()).get(); + val market = marketFactory + .buildMarketCancel(SportEvents.any(), oddEvenMarket(), anyProducerId()) + .get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForDefaultLanguage(aLanguage, willRespectSdkStrategy); + assertThat(market).getNameForDefault(aLanguage, willFailRespectingSdkStrategy); } @Test @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsInNonDefaultLanguage( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) { val langA = uniqueLanguages.getOne(); val langB = uniqueLanguages.getOne(); @@ -80,10 +78,11 @@ public void marketNameRetrievalFailsInNonDefaultLanguage( .withDefaultLanguage(langA) .build(); - val market = marketFactory.buildMarketCancel(SportEvents.any(), oddEven(), anyProducerId()).get(); + val market = marketFactory + .buildMarketCancel(SportEvents.any(), oddEvenMarket(), anyProducerId()) + .get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForNonDefaultLanguage(langB, willRespectSdkStrategy); + assertThat(market).getNameForGiven(langB, willFailRespectingSdkStrategy); } @Test @@ -101,7 +100,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguage( .build(); val market = marketFactory - .buildMarketCancel(SportEvents.any(), oddEven(), nonPremiumCricketProducerId) + .buildMarketCancel(SportEvents.any(), oddEvenMarket(), nonPremiumCricketProducerId) .get(); val definition = market.getMarketDefinition(); @@ -127,7 +126,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguageForPremiumCricketPro .build(); val market = marketFactory - .buildMarketCancel(SportEvents.any(), oddEven(), PREMIUM_CRICKET_PRODUCER_ID) + .buildMarketCancel(SportEvents.any(), oddEvenMarket(), PREMIUM_CRICKET_PRODUCER_ID) .get(); val definition = market.getMarketDefinition(); @@ -157,7 +156,7 @@ public void marketDefinitionIsNotDescribedForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketCancel(SportEvents.any(), oddEven(), nonPremiumCricketProducerId) + .buildMarketCancel(SportEvents.any(), oddEvenMarket(), nonPremiumCricketProducerId) .get(); val definition = market.getMarketDefinition(); @@ -184,7 +183,7 @@ public void marketDefinitionIsNotDescribedForPremiumCricketProducerForNonDefault .build(); val market = marketFactory - .buildMarketCancel(SportEvents.any(), oddEven(), PREMIUM_CRICKET_PRODUCER_ID) + .buildMarketCancel(SportEvents.any(), oddEvenMarket(), PREMIUM_CRICKET_PRODUCER_ID) .get(); val definition = market.getMarketDefinition(); @@ -227,13 +226,15 @@ public void marketHasNameForDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); - val market = marketFactory.buildMarketCancel(SportEvents.any(), oddEven(), anyProducerId()).get(); + val market = marketFactory + .buildMarketCancel(SportEvents.any(), oddEvenMarket(), anyProducerId()) + .get(); - assertThat(market).hasName(of(oddEvenDescription().getName(), in(langB))); + assertThat(market).hasName(of(oddEvenMarketDescription().getName(), in(langB))); } @Test @@ -242,14 +243,16 @@ public void definitionHasNameTemplateForNonDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); - val market = marketFactory.buildMarketCancel(SportEvents.any(), oddEven(), anyProducerId()).get(); + val market = marketFactory + .buildMarketCancel(SportEvents.any(), oddEvenMarket(), anyProducerId()) + .get(); assertThat(market.getMarketDefinition().getNameTemplate(langB)) - .isEqualTo(oddEvenDescription().getName()); + .isEqualTo(oddEvenMarketDescription().getName()); } } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplSimpleTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplSimpleTest.java index 05401e57..c84de887 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplSimpleTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplSimpleTest.java @@ -3,11 +3,11 @@ */ package com.sportradar.unifiedodds.sdk.impl.oddsentities.markets; -import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptions.namesOf; +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionFactory.namesOf; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.noMarketDescribingProvider; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.providing; -import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.oddEvenDescription; -import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.Simple.oddEven; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.Simple.oddEvenMarket; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_CATCH_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketAssert.assertThat; @@ -17,14 +17,11 @@ import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketFactories.BuilderStubbingOutSportEventAndCaches.stubbingOutSportEventAndCaches; import static com.sportradar.utils.domain.names.LanguageHolder.in; import static com.sportradar.utils.domain.names.TranslationHolder.of; -import static com.sportradar.utils.domain.names.TranslationHolder.with; import static com.sportradar.utils.domain.producers.ProducerIds.PREMIUM_CRICKET_PRODUCER_ID; import static com.sportradar.utils.domain.producers.ProducerIds.anyProducerId; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; -import com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptions; import com.sportradar.unifiedodds.sdk.entities.SportEvents; import com.sportradar.utils.domain.UniqueObjects; import com.sportradar.utils.domain.names.Languages; @@ -51,7 +48,7 @@ public static class WhenMarketIsNotBackedWithMarketDescription { @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsInDefaultLanguage( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) { val aLanguage = Languages.any(); val marketFactory = stubbingOutSportEventAndCaches() @@ -60,17 +57,16 @@ public void marketNameRetrievalFailsInDefaultLanguage( .with(exceptionHandlingStrategy) .build(); - val market = marketFactory.buildMarket(SportEvents.any(), oddEven(), anyProducerId()).get(); + val market = marketFactory.buildMarket(SportEvents.any(), oddEvenMarket(), anyProducerId()).get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForDefaultLanguage(aLanguage, willRespectSdkStrategy); + assertThat(market).getNameForDefault(aLanguage, willFailRespectingSdkStrategy); } @Test @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsInNonDefaultLanguage( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) { val langA = uniqueLanguages.getOne(); val langB = uniqueLanguages.getOne(); @@ -80,10 +76,9 @@ public void marketNameRetrievalFailsInNonDefaultLanguage( .withDefaultLanguage(langA) .build(); - val market = marketFactory.buildMarket(SportEvents.any(), oddEven(), anyProducerId()).get(); + val market = marketFactory.buildMarket(SportEvents.any(), oddEvenMarket(), anyProducerId()).get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForNonDefaultLanguage(langB, willRespectSdkStrategy); + assertThat(market).getNameForGiven(langB, willFailRespectingSdkStrategy); } @Test @@ -101,7 +96,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguage( .build(); val market = marketFactory - .buildMarket(SportEvents.any(), oddEven(), nonPremiumCricketProducerId) + .buildMarket(SportEvents.any(), oddEvenMarket(), nonPremiumCricketProducerId) .get(); val definition = market.getMarketDefinition(); @@ -127,7 +122,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguageForPremiumCricketPro .build(); val market = marketFactory - .buildMarket(SportEvents.any(), oddEven(), PREMIUM_CRICKET_PRODUCER_ID) + .buildMarket(SportEvents.any(), oddEvenMarket(), PREMIUM_CRICKET_PRODUCER_ID) .get(); val definition = market.getMarketDefinition(); @@ -157,7 +152,7 @@ public void marketDefinitionIsNotDescribedForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarket(SportEvents.any(), oddEven(), nonPremiumCricketProducerId) + .buildMarket(SportEvents.any(), oddEvenMarket(), nonPremiumCricketProducerId) .get(); val definition = market.getMarketDefinition(); @@ -184,7 +179,7 @@ public void marketDefinitionIsNotDescribedForPremiumCricketProducerForNonDefault .build(); val market = marketFactory - .buildMarket(SportEvents.any(), oddEven(), PREMIUM_CRICKET_PRODUCER_ID) + .buildMarket(SportEvents.any(), oddEvenMarket(), PREMIUM_CRICKET_PRODUCER_ID) .get(); val definition = market.getMarketDefinition(); @@ -227,13 +222,13 @@ public void marketHasNameForDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); - val market = marketFactory.buildMarket(SportEvents.any(), oddEven(), anyProducerId()).get(); + val market = marketFactory.buildMarket(SportEvents.any(), oddEvenMarket(), anyProducerId()).get(); - assertThat(market).hasName(of(oddEvenDescription().getName(), in(langB))); + assertThat(market).hasName(of(oddEvenMarketDescription().getName(), in(langB))); } @Test @@ -242,14 +237,14 @@ public void definitionHasNameTemplateForNonDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); - val market = marketFactory.buildMarket(SportEvents.any(), oddEven(), anyProducerId()).get(); + val market = marketFactory.buildMarket(SportEvents.any(), oddEvenMarket(), anyProducerId()).get(); assertThat(market.getMarketDefinition().getNameTemplate(langB)) - .isEqualTo(oddEvenDescription().getName()); + .isEqualTo(oddEvenMarketDescription().getName()); } } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithOddsTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithOddsTest.java index a77254f7..3147217e 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithOddsTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithOddsTest.java @@ -3,12 +3,12 @@ */ package com.sportradar.unifiedodds.sdk.impl.oddsentities.markets; -import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptions.namesOf; +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionFactory.namesOf; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.noMarketDescribingProvider; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.providing; -import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.oddEvenDescription; -import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.anytimeGoalscorer; -import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.oddEven; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.anytimeGoalscorerMarket; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.oddEvenMarket; import static com.sportradar.unifiedodds.sdk.entities.SportEvents.anyStage; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_CATCH_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; @@ -20,12 +20,10 @@ import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.OutcomeOddsAssert.assertThat; import static com.sportradar.utils.domain.names.LanguageHolder.in; import static com.sportradar.utils.domain.names.TranslationHolder.of; -import static com.sportradar.utils.domain.names.TranslationHolder.with; import static com.sportradar.utils.domain.producers.ProducerIds.*; import static org.assertj.core.api.Assertions.assertThat; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; -import com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptions; import com.sportradar.unifiedodds.sdk.entities.SportEvents; import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithOdds; import com.sportradar.utils.domain.UniqueObjects; @@ -46,7 +44,7 @@ public class MarketFactoryImplWithOddsTest { private static MarketWithOdds buildAnytimeGoalscorerMarketVia(MarketFactory marketFactory) { return marketFactory - .buildMarketWithOdds(SportEvents.anyMatch(), anytimeGoalscorer(), anyProducerId()) + .buildMarketWithOdds(SportEvents.anyMatch(), anytimeGoalscorerMarket(), anyProducerId()) .get(); } @@ -59,7 +57,7 @@ public static class WhenMarketIsNotBackedWithMarketDescription { @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsForDefaultLanguage( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) { val aLanguage = Languages.any(); val marketFactory = stubbingOutSportEventAndCaches() @@ -69,18 +67,17 @@ public void marketNameRetrievalFailsForDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForDefaultLanguage(aLanguage, willRespectSdkStrategy); + assertThat(market).getNameForDefault(aLanguage, willFailRespectingSdkStrategy); } @Test @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsForNonDefaultLanguage( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) { val langA = uniqueLanguages.getOne(); val langB = uniqueLanguages.getOne(); @@ -91,11 +88,10 @@ public void marketNameRetrievalFailsForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForNonDefaultLanguage(langB, willRespectSdkStrategy); + assertThat(market).getNameForGiven(langB, willFailRespectingSdkStrategy); } @Test @@ -113,7 +109,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), nonPremiumCricketProducerId) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), nonPremiumCricketProducerId) .get(); val definition = market.getMarketDefinition(); @@ -139,7 +135,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguageForPremiumCricketPro .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), PREMIUM_CRICKET_PRODUCER_ID) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), PREMIUM_CRICKET_PRODUCER_ID) .get(); val definition = market.getMarketDefinition(); @@ -169,7 +165,7 @@ public void marketDefinitionIsNotDescribedForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), nonPremiumCricketProducerId) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), nonPremiumCricketProducerId) .get(); val definition = market.getMarketDefinition(); @@ -196,7 +192,7 @@ public void marketDefinitionIsNotDescribedForPremiumCricketProducerForNonDefault .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), PREMIUM_CRICKET_PRODUCER_ID) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), PREMIUM_CRICKET_PRODUCER_ID) .get(); val definition = market.getMarketDefinition(); @@ -223,7 +219,7 @@ public void nonPlayerOutcomeDefinitionIsNotDescribedForDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val oneOfOutcomes = market.getOutcomeOdds().get(0); @@ -247,7 +243,7 @@ public void nonPlayerOutcomeDefinitionIsNotDescribedForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val oneOfOutcomes = market.getOutcomeOdds().get(0); @@ -268,7 +264,7 @@ public void nonPlayerOutcomeIsCreatedForTeamOutcomeForNonMatchSportEvent() { .build(); val market = marketFactory - .buildMarketWithOdds(anyStage(), anytimeGoalscorer(), anyProducerId()) + .buildMarketWithOdds(anyStage(), anytimeGoalscorerMarket(), anyProducerId()) .get(); val oneOfOutcomes = market.getOutcomeOdds().get(0); @@ -335,7 +331,7 @@ public void outcomeNameRetrievalFailsInDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = market.getOutcomeOdds().get(0); @@ -359,7 +355,7 @@ public void outcomeNameRetrievalFailsInNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = market.getOutcomeOdds().get(0); @@ -398,15 +394,15 @@ public void marketHasNameForDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); - assertThat(market).hasName(of(oddEvenDescription().getName(), in(langB))); + assertThat(market).hasName(of(oddEvenMarketDescription().getName(), in(langB))); } @Test @@ -415,16 +411,16 @@ public void definitionHasNameTemplateForNonDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); val market = marketFactory - .buildMarketWithOdds(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithOdds(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); assertThat(market.getMarketDefinition().getNameTemplate(langB)) - .isEqualTo(oddEvenDescription().getName()); + .isEqualTo(oddEvenMarketDescription().getName()); } } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithProbabilitiesTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithProbabilitiesTest.java index b834f525..09b8d7df 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithProbabilitiesTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithProbabilitiesTest.java @@ -3,12 +3,13 @@ */ package com.sportradar.unifiedodds.sdk.impl.oddsentities.markets; -import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptions.namesOf; +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionFactory.namesOf; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.*; -import static com.sportradar.unifiedodds.sdk.conn.OddEvenMarket.EVEN_OUTCOME_ID; -import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.evenOutcomeDescription; -import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.oddEvenDescription; -import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.oddEven; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.evenOutcomeDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.removeAllOutcomesFrom; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithOdds.oddEvenMarket; +import static com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds.EVEN_OUTCOME_ID; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_CATCH_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketAssert.assertThat; @@ -30,7 +31,8 @@ import com.sportradar.uf.sportsapi.datamodel.DescMarket; import com.sportradar.uf.sportsapi.datamodel.DescOutcomes; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; -import com.sportradar.unifiedodds.sdk.conn.OddEvenMarket; +import com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions; +import com.sportradar.unifiedodds.sdk.conn.marketids.OddEvenMarketIds; import com.sportradar.unifiedodds.sdk.entities.SportEvents; import com.sportradar.unifiedodds.sdk.oddsentities.MarketWithProbabilities; import com.sportradar.unifiedodds.sdk.oddsentities.OutcomeProbabilities; @@ -59,12 +61,7 @@ public class MarketFactoryImplWithProbabilitiesTest { private MarketFactoryImplWithProbabilitiesTest() {} private static OutcomeProbabilities getEvenOutcomeFrom(MarketWithProbabilities market) { - return market.getOutcomeProbabilities().stream().filter(OddEvenMarket::isEven).findFirst().get(); - } - - private static DescMarket removeAllOutcomesFrom(DescMarket oddEvenDescription) { - oddEvenDescription.getOutcomes().getOutcome().clear(); - return oddEvenDescription; + return market.getOutcomeProbabilities().stream().filter(OddEvenMarketIds::isEven).findFirst().get(); } private static DescMarket removeEvenOutcomeFrom(DescMarket oddEvenDescription) { @@ -89,7 +86,7 @@ public static class WhenMarketIsNotBackedWithMarketDescription { @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsForDefaultLanguage( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) { val aLanguage = Languages.any(); val marketFactory = stubbingOutSportEventAndCaches() @@ -99,18 +96,17 @@ public void marketNameRetrievalFailsForDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForDefaultLanguage(aLanguage, willRespectSdkStrategy); + assertThat(market).getNameForDefault(aLanguage, willFailRespectingSdkStrategy); } @Test @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsForNonDefaultLanguage( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) { val langA = uniqueLanguages.getOne(); val langB = uniqueLanguages.getOne(); @@ -121,11 +117,10 @@ public void marketNameRetrievalFailsForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForNonDefaultLanguage(langB, willRespectSdkStrategy); + assertThat(market).getNameForGiven(langB, willFailRespectingSdkStrategy); } @Test @@ -143,7 +138,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), nonPremiumCricketProducerId) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), nonPremiumCricketProducerId) .get(); val definition = market.getMarketDefinition(); @@ -169,7 +164,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguageForPremiumCricketPro .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), PREMIUM_CRICKET_PRODUCER_ID) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), PREMIUM_CRICKET_PRODUCER_ID) .get(); val definition = market.getMarketDefinition(); @@ -199,7 +194,7 @@ public void marketDefinitionIsNotDescribedForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), nonPremiumCricketProducerId) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), nonPremiumCricketProducerId) .get(); val definition = market.getMarketDefinition(); @@ -226,7 +221,7 @@ public void marketDefinitionIsNotDescribedForPremiumCricketProducerForNonDefault .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), PREMIUM_CRICKET_PRODUCER_ID) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), PREMIUM_CRICKET_PRODUCER_ID) .get(); val definition = market.getMarketDefinition(); @@ -253,7 +248,7 @@ public void outcomeDefinitionIsNotDescribedForDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val oneOfOutcomes = market.getOutcomeProbabilities().get(0); @@ -276,7 +271,7 @@ public void outcomeDefinitionIsNotDescribedForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val oneOfOutcomes = market.getOutcomeProbabilities().get(0); @@ -301,7 +296,7 @@ public void outcomeNameRetrievalFailsInDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = market.getOutcomeProbabilities().get(0); @@ -324,7 +319,7 @@ public void outcomeNameRetrievalFailsInNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = market.getOutcomeProbabilities().get(0); @@ -369,7 +364,7 @@ public void outcomeNameRetrievalFails( ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy ) { val aLanguage = Languages.any(); - val faultyDescription = removeAllOutcomesFrom(oddEvenDescription()); + val faultyDescription = removeAllOutcomesFrom(oddEvenMarketDescription()); val marketFactory = stubbingOutSportEventAndCaches() .with(providing(in(aLanguage), namesOf(faultyDescription, in(aLanguage)))) .withDefaultLanguage(aLanguage) @@ -377,7 +372,7 @@ public void outcomeNameRetrievalFails( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -393,7 +388,7 @@ public void outcomeNameRetrievalFailsForNonDefaultLanguage( ) { val langA = uniqueLanguages.getOne(); val langB = uniqueLanguages.getOne(); - val faultyDescription = removeAllOutcomesFrom(oddEvenDescription()); + val faultyDescription = removeAllOutcomesFrom(oddEvenMarketDescription()); val marketFactory = stubbingOutSportEventAndCaches() .with(providing(in(langB), namesOf(faultyDescription, in(langB)))) .withDefaultLanguage(langA) @@ -401,7 +396,7 @@ public void outcomeNameRetrievalFailsForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -416,7 +411,7 @@ public void outcomeNameRetrievalFailsDueToMissingOutcomeInSpiteOfReloadOfMarkets ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy ) { val language = Languages.any(); - val faultyDescription = namesOf(removeAllOutcomesFrom(oddEvenDescription()), in(language)); + val faultyDescription = namesOf(removeAllOutcomesFrom(oddEvenMarketDescription()), in(language)); val descriptionProvider = providing(in(language), faultyDescription); val marketFactory = stubbingOutSportEventAndCaches() .with(descriptionProvider) @@ -426,7 +421,7 @@ public void outcomeNameRetrievalFailsDueToMissingOutcomeInSpiteOfReloadOfMarkets .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -438,8 +433,8 @@ public void outcomeNameRetrievalFailsDueToMissingOutcomeInSpiteOfReloadOfMarkets @Test public void reloadingMarketsRepairsMissingOutcome() { val language = ENGLISH; - val faultyDescription = namesOf(removeAllOutcomesFrom(oddEvenDescription()), in(language)); - val repairedDescription = namesOf(oddEvenDescription(), in(language)); + val faultyDescription = namesOf(removeAllOutcomesFrom(oddEvenMarketDescription()), in(language)); + val repairedDescription = namesOf(oddEvenMarketDescription(), in(language)); val descriptionProvider = providing( in(language), faultyDescription, @@ -453,7 +448,7 @@ public void reloadingMarketsRepairsMissingOutcome() { .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -468,7 +463,7 @@ public void reloadingOfMarketsAreThrottledTo1CallPer30secondsTimeframe( ExpectationTowardsSdkErrorHandlingStrategy notUsed ) { val language = Languages.any(); - val faultyDescription = namesOf(removeAllOutcomesFrom(oddEvenDescription()), in(language)); + val faultyDescription = namesOf(removeAllOutcomesFrom(oddEvenMarketDescription()), in(language)); val descriptionProvider = providing(in(language), faultyDescription); val marketFactory = stubbingOutSportEventAndCaches() .with(descriptionProvider) @@ -478,7 +473,7 @@ public void reloadingOfMarketsAreThrottledTo1CallPer30secondsTimeframe( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); getName(outcome); @@ -495,7 +490,7 @@ public void throttlingAllowsToReloadMarketsAgainAfter30secondsTimeframePasses( ExpectationTowardsSdkErrorHandlingStrategy notUsed ) { val language = Languages.any(); - val faultyDescription = namesOf(removeAllOutcomesFrom(oddEvenDescription()), in(language)); + val faultyDescription = namesOf(removeAllOutcomesFrom(oddEvenMarketDescription()), in(language)); val descriptionProvider = providing(in(language), faultyDescription); val marketFactory = stubbingOutSportEventAndCaches() .with(descriptionProvider) @@ -505,7 +500,7 @@ public void throttlingAllowsToReloadMarketsAgainAfter30secondsTimeframePasses( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); getName(outcome); @@ -516,7 +511,12 @@ public void throttlingAllowsToReloadMarketsAgainAfter30secondsTimeframePasses( } private OutcomeProbabilities getEvenOutcomeFrom(MarketWithProbabilities market) { - return market.getOutcomeProbabilities().stream().filter(OddEvenMarket::isEven).findFirst().get(); + return market + .getOutcomeProbabilities() + .stream() + .filter(OddEvenMarketIds::isEven) + .findFirst() + .get(); } private Object[] exceptionHandlingStrategies() { @@ -544,7 +544,7 @@ public void outcomeNameRetrievalFails( ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy ) { val aLanguage = Languages.any(); - val faultyDescription = removeEvenOutcomeFrom(oddEvenDescription()); + val faultyDescription = removeEvenOutcomeFrom(oddEvenMarketDescription()); val marketFactory = stubbingOutSportEventAndCaches() .with(providing(in(aLanguage), namesOf(faultyDescription, in(aLanguage)))) .withDefaultLanguage(aLanguage) @@ -552,7 +552,7 @@ public void outcomeNameRetrievalFails( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -568,7 +568,7 @@ public void outcomeNameRetrievalFailsForNonDefaultLanguage( ) { val langA = uniqueLanguages.getOne(); val langB = uniqueLanguages.getOne(); - val faultyDescription = removeEvenOutcomeFrom(oddEvenDescription()); + val faultyDescription = removeEvenOutcomeFrom(oddEvenMarketDescription()); val marketFactory = stubbingOutSportEventAndCaches() .with(providing(in(langB), namesOf(faultyDescription, in(langB)))) .withDefaultLanguage(langA) @@ -576,7 +576,7 @@ public void outcomeNameRetrievalFailsForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -591,7 +591,7 @@ public void outcomeNameRetrievalFailsDueToMissingOutcomeInSpiteOfReloadOfMarkets ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy ) { val language = Languages.any(); - val faultyDescription = namesOf(removeEvenOutcomeFrom(oddEvenDescription()), in(language)); + val faultyDescription = namesOf(removeEvenOutcomeFrom(oddEvenMarketDescription()), in(language)); val descriptionProvider = providing(in(language), faultyDescription); val marketFactory = stubbingOutSportEventAndCaches() .with(descriptionProvider) @@ -601,7 +601,7 @@ public void outcomeNameRetrievalFailsDueToMissingOutcomeInSpiteOfReloadOfMarkets .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -613,8 +613,8 @@ public void outcomeNameRetrievalFailsDueToMissingOutcomeInSpiteOfReloadOfMarkets @Test public void reloadingMarketsRepairsMissingOutcome() { val language = ENGLISH; - val faultyDescription = namesOf(removeEvenOutcomeFrom(oddEvenDescription()), in(language)); - val repairedDescription = namesOf(oddEvenDescription(), in(language)); + val faultyDescription = namesOf(removeEvenOutcomeFrom(oddEvenMarketDescription()), in(language)); + val repairedDescription = namesOf(oddEvenMarketDescription(), in(language)); val descriptionProvider = providing( in(language), faultyDescription, @@ -628,7 +628,7 @@ public void reloadingMarketsRepairsMissingOutcome() { .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -643,7 +643,7 @@ public void reloadingOfMarketsAreThrottledTo1CallPer30secondsTimeframe( ExpectationTowardsSdkErrorHandlingStrategy notUsed ) { val language = Languages.any(); - val faultyDescription = namesOf(removeEvenOutcomeFrom(oddEvenDescription()), in(language)); + val faultyDescription = namesOf(removeEvenOutcomeFrom(oddEvenMarketDescription()), in(language)); val descriptionProvider = providing(in(language), faultyDescription); val marketFactory = stubbingOutSportEventAndCaches() .with(descriptionProvider) @@ -653,7 +653,7 @@ public void reloadingOfMarketsAreThrottledTo1CallPer30secondsTimeframe( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); getName(outcome); @@ -670,7 +670,7 @@ public void throttlingAllowsToReloadMarketsAgainAfter30secondsTimeframePasses( ExpectationTowardsSdkErrorHandlingStrategy notUsed ) { val language = Languages.any(); - val faultyDescription = namesOf(removeEvenOutcomeFrom(oddEvenDescription()), in(language)); + val faultyDescription = namesOf(removeEvenOutcomeFrom(oddEvenMarketDescription()), in(language)); val descriptionProvider = providing(in(language), faultyDescription); val marketFactory = stubbingOutSportEventAndCaches() .with(descriptionProvider) @@ -680,7 +680,7 @@ public void throttlingAllowsToReloadMarketsAgainAfter30secondsTimeframePasses( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); getName(outcome); @@ -722,9 +722,9 @@ public void outcomeNameRetrievalFails( val langB = ENGLISH; val marketDescription = namesOf( - oddEvenDescription(langA), + oddEvenMarketDescription(langA), in(langA), - removeEvenOutcomeFrom(oddEvenDescription(langB)), + removeEvenOutcomeFrom(oddEvenMarketDescription(langB)), in(langB) ); val marketFactory = stubbingOutSportEventAndCaches() @@ -734,7 +734,7 @@ public void outcomeNameRetrievalFails( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -750,7 +750,7 @@ public void outcomeNameRetrievalFailsForNonDefaultLanguage( ) { val langA = uniqueLanguages.getOne(); val langB = uniqueLanguages.getOne(); - val faultyDescription = removeEvenOutcomeFrom(oddEvenDescription()); + val faultyDescription = removeEvenOutcomeFrom(oddEvenMarketDescription()); val marketFactory = stubbingOutSportEventAndCaches() .with(providing(in(langB), namesOf(faultyDescription, in(langB)))) .withDefaultLanguage(langA) @@ -758,7 +758,7 @@ public void outcomeNameRetrievalFailsForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); @@ -768,78 +768,7 @@ public void outcomeNameRetrievalFailsForNonDefaultLanguage( @Test @Parameters(method = "exceptionHandlingStrategies") - public void outcomeNameRetrievalFailsInSpiteOfReloadOfMarkets( - ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy - ) { - val langA = FRENCH; - val langB = ENGLISH; - - val marketDescription = namesOf( - oddEvenDescription(langA), - in(langA), - removeEvenOutcomeFrom(oddEvenDescription(langB)), - in(langB) - ); - val descriptionProvider = providing(in(langA), in(langB), marketDescription); - val marketFactory = stubbingOutSportEventAndCaches() - .with(descriptionProvider) - .withDefaultLanguage(langB) - .with(exceptionHandlingStrategy) - .with(time) - .build(); - - val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) - .get(); - val outcome = getEvenOutcomeFrom(market); - - assertThat(outcome) - .nameIsNotBackedByOutcomeDescriptionForDefaultLanguage(langB, willRespectSdkStrategy); - descriptionProvider.verifyDescriptionWasReloaded(); - } - - @Test - public void reloadingMarketsRepairsUntranslatedOutcome() { - val langA = FRENCH; - val langB = ENGLISH; - - val faultyDescription = namesOf( - oddEvenDescription(langA), - in(langA), - removeEvenOutcomeFrom(oddEvenDescription(langB)), - in(langB) - ); - val repairedDescription = namesOf( - oddEvenDescription(langA), - in(langA), - oddEvenDescription(langB), - in(langB) - ); - val descriptionProvider = providing( - in(langA), - in(langB), - faultyDescription, - andAfterReloading(in(langA), in(langB), repairedDescription) - ); - val marketFactory = stubbingOutSportEventAndCaches() - .with(descriptionProvider) - .withDefaultLanguage(langB) - .with(time) - .build(); - - val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) - .get(); - val outcome = getEvenOutcomeFrom(market); - - assertThat(outcome) - .hasNameInDefaultLanguage(of(evenOutcomeDescription(langB).getName(), in(langB))); - } - - @Test - @Parameters(method = "exceptionHandlingStrategies") - public void reloadingOfMarketsAreThrottledTo1CallPer30secondsTimeframe( + public void marketsAreNotReloaded( ExceptionHandlingStrategy exceptionHandlingStrategy, ExpectationTowardsSdkErrorHandlingStrategy notUsed ) { @@ -847,9 +776,9 @@ public void reloadingOfMarketsAreThrottledTo1CallPer30secondsTimeframe( val langB = ENGLISH; val marketDescription = namesOf( - oddEvenDescription(langA), + oddEvenMarketDescription(langA), in(langA), - removeEvenOutcomeFrom(oddEvenDescription(langB)), + removeEvenOutcomeFrom(oddEvenMarketDescription(langB)), in(langB) ); val descriptionProvider = providing(in(langA), in(langB), marketDescription); @@ -861,48 +790,14 @@ public void reloadingOfMarketsAreThrottledTo1CallPer30secondsTimeframe( .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); getName(outcome); time.tick(CIRCUIT_BREAKER_OPEN_PERIOD.minus(seconds(1))); getName(outcome); - descriptionProvider.verifyDescriptionWasReloadedTimes(1); - } - - @Test - @Parameters(method = "exceptionHandlingStrategies") - public void throttlingAllowsToReloadMarketsAgainAfter30secondsTimeframePasses( - ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy notUsed - ) { - val langA = FRENCH; - val langB = ENGLISH; - - val marketDescription = namesOf( - oddEvenDescription(langA), - in(langA), - removeEvenOutcomeFrom(oddEvenDescription(langB)), - in(langB) - ); - val descriptionProvider = providing(in(langA), in(langB), marketDescription); - val marketFactory = stubbingOutSportEventAndCaches() - .with(descriptionProvider) - .withDefaultLanguage(langB) - .with(exceptionHandlingStrategy) - .with(time) - .build(); - - val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) - .get(); - val outcome = getEvenOutcomeFrom(market); - getName(outcome); - time.tick(CIRCUIT_BREAKER_OPEN_PERIOD.plus(seconds(1))); - getName(outcome); - - descriptionProvider.verifyDescriptionWasReloadedTimes(2); + descriptionProvider.verifyDescriptionWasReloadedTimes(0); } private Object[] exceptionHandlingStrategies() { @@ -923,15 +818,15 @@ public void marketHasNameForDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); - assertThat(market).hasName(of(oddEvenDescription().getName(), in(langB))); + assertThat(market).hasName(of(oddEvenMarketDescription().getName(), in(langB))); } @Test @@ -940,33 +835,35 @@ public void definitionHasNameTemplateForNonDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); assertThat(market.getMarketDefinition().getNameTemplate(langB)) - .isEqualTo(oddEvenDescription().getName()); + .isEqualTo(oddEvenMarketDescription().getName()); } @Test public void outcomeHasNameForDefaultLanguage() { val aLanguage = Languages.any(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(aLanguage), namesOf(oddEvenDescription(), in(aLanguage)))) + .with(providing(in(aLanguage), namesOf(oddEvenMarketDescription(), in(aLanguage)))) .withDefaultLanguage(aLanguage) .build(); val market = marketFactory - .buildMarketWithProbabilities(SportEvents.any(), oddEven(), anyProducerId()) + .buildMarketWithProbabilities(SportEvents.any(), oddEvenMarket(), anyProducerId()) .get(); val outcome = getEvenOutcomeFrom(market); assertThat(outcome) - .hasNameInDefaultLanguage(of(evenOutcomeDescription().getName(), in(aLanguage))); + .hasNameInDefaultLanguage( + of(SapiMarketDescriptions.OddEven.evenOutcomeDescription().getName(), in(aLanguage)) + ); } } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithSettlementTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithSettlementTest.java index a9f41957..b9b59cef 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithSettlementTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/MarketFactoryImplWithSettlementTest.java @@ -3,11 +3,11 @@ */ package com.sportradar.unifiedodds.sdk.impl.oddsentities.markets; -import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptions.namesOf; +import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptionFactory.namesOf; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.noMarketDescribingProvider; import static com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptorProviders.providing; -import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.oddEvenDescription; -import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithSettlementOutcomes.oddEvenWhereWonOdd; +import static com.sportradar.unifiedodds.sdk.conn.SapiMarketDescriptions.OddEven.oddEvenMarketDescription; +import static com.sportradar.unifiedodds.sdk.conn.UfMarkets.WithSettlementOutcomes.oddEvenMarketWhereWonOdd; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_CATCH_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.MarketAssert.assertThat; @@ -18,14 +18,11 @@ import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.OutcomeAssert.assertThat; import static com.sportradar.utils.domain.names.LanguageHolder.in; import static com.sportradar.utils.domain.names.TranslationHolder.of; -import static com.sportradar.utils.domain.names.TranslationHolder.with; import static com.sportradar.utils.domain.producers.ProducerIds.PREMIUM_CRICKET_PRODUCER_ID; import static com.sportradar.utils.domain.producers.ProducerIds.anyProducerId; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; -import com.sportradar.unifiedodds.sdk.caching.markets.MarketDescriptions; import com.sportradar.unifiedodds.sdk.entities.SportEvents; import com.sportradar.utils.domain.UniqueObjects; import com.sportradar.utils.domain.names.Languages; @@ -52,7 +49,7 @@ public static class WhenMarketIsNotBackedWithMarketDescription { @Parameters(method = "exceptionHandlingStrategies") public void marketNameRetrievalFailsInDefaultLanguage( ExceptionHandlingStrategy exceptionHandlingStrategy, - ExpectationTowardsSdkErrorHandlingStrategy willRespectSdkStrategy + ExpectationTowardsSdkErrorHandlingStrategy willFailRespectingSdkStrategy ) { val aLanguage = Languages.any(); val marketFactory = stubbingOutSportEventAndCaches() @@ -62,11 +59,10 @@ public void marketNameRetrievalFailsInDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithSettlement(SportEvents.any(), oddEvenWhereWonOdd(), anyProducerId()) + .buildMarketWithSettlement(SportEvents.any(), oddEvenMarketWhereWonOdd(), anyProducerId()) .get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForDefaultLanguage(aLanguage, willRespectSdkStrategy); + assertThat(market).getNameForDefault(aLanguage, willFailRespectingSdkStrategy); } @Test @@ -84,11 +80,10 @@ public void marketNameRetrievalFailsInNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithSettlement(SportEvents.any(), oddEvenWhereWonOdd(), anyProducerId()) + .buildMarketWithSettlement(SportEvents.any(), oddEvenMarketWhereWonOdd(), anyProducerId()) .get(); - assertThat(market) - .nameIsNotBackedByMarketDescriptionForNonDefaultLanguage(langB, willRespectSdkStrategy); + assertThat(market).getNameForGiven(langB, willRespectSdkStrategy); } @Test @@ -108,7 +103,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguage( val market = marketFactory .buildMarketWithSettlement( SportEvents.any(), - oddEvenWhereWonOdd(), + oddEvenMarketWhereWonOdd(), nonPremiumCricketProducerId ) .get(); @@ -138,7 +133,7 @@ public void marketDefinitionIsNotDescribedForDefaultLanguageForPremiumCricketPro val market = marketFactory .buildMarketWithSettlement( SportEvents.any(), - oddEvenWhereWonOdd(), + oddEvenMarketWhereWonOdd(), PREMIUM_CRICKET_PRODUCER_ID ) .get(); @@ -172,7 +167,7 @@ public void marketDefinitionIsNotDescribedForNonDefaultLanguage( val market = marketFactory .buildMarketWithSettlement( SportEvents.any(), - oddEvenWhereWonOdd(), + oddEvenMarketWhereWonOdd(), nonPremiumCricketProducerId ) .get(); @@ -203,7 +198,7 @@ public void marketDefinitionIsNotDescribedForPremiumCricketProducerForNonDefault val market = marketFactory .buildMarketWithSettlement( SportEvents.any(), - oddEvenWhereWonOdd(), + oddEvenMarketWhereWonOdd(), PREMIUM_CRICKET_PRODUCER_ID ) .get(); @@ -232,7 +227,7 @@ public void outcomeDefinitionIsNotDescribedForDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithSettlement(SportEvents.any(), oddEvenWhereWonOdd(), anyProducerId()) + .buildMarketWithSettlement(SportEvents.any(), oddEvenMarketWhereWonOdd(), anyProducerId()) .get(); val oneOfOutcomes = market.getOutcomeSettlements().get(0); @@ -255,7 +250,7 @@ public void outcomeDefinitionIsNotDescribedForNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithSettlement(SportEvents.any(), oddEvenWhereWonOdd(), anyProducerId()) + .buildMarketWithSettlement(SportEvents.any(), oddEvenMarketWhereWonOdd(), anyProducerId()) .get(); val oneOfOutcomes = market.getOutcomeSettlements().get(0); @@ -280,7 +275,7 @@ public void outcomeNameRetrievalFailsInDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithSettlement(SportEvents.any(), oddEvenWhereWonOdd(), anyProducerId()) + .buildMarketWithSettlement(SportEvents.any(), oddEvenMarketWhereWonOdd(), anyProducerId()) .get(); val outcome = market.getOutcomeSettlements().get(0); @@ -303,7 +298,7 @@ public void outcomeNameRetrievalFailsInNonDefaultLanguage( .build(); val market = marketFactory - .buildMarketWithSettlement(SportEvents.any(), oddEvenWhereWonOdd(), anyProducerId()) + .buildMarketWithSettlement(SportEvents.any(), oddEvenMarketWhereWonOdd(), anyProducerId()) .get(); val outcome = market.getOutcomeSettlements().get(0); @@ -341,15 +336,15 @@ public void marketHasNameForDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); val market = marketFactory - .buildMarketWithSettlement(SportEvents.any(), oddEvenWhereWonOdd(), anyProducerId()) + .buildMarketWithSettlement(SportEvents.any(), oddEvenMarketWhereWonOdd(), anyProducerId()) .get(); - assertThat(market).hasName(of(oddEvenDescription().getName(), in(langB))); + assertThat(market).hasName(of(oddEvenMarketDescription().getName(), in(langB))); } @Test @@ -358,16 +353,16 @@ public void definitionHasNameTemplateForNonDefaultLanguage() { val langB = uniqueLanguages.getOne(); val marketFactory = stubbingOutSportEventAndCaches() - .with(providing(in(langB), namesOf(oddEvenDescription(), in(langB)))) + .with(providing(in(langB), namesOf(oddEvenMarketDescription(), in(langB)))) .withDefaultLanguage(langA) .build(); val market = marketFactory - .buildMarketWithSettlement(SportEvents.any(), oddEvenWhereWonOdd(), anyProducerId()) + .buildMarketWithSettlement(SportEvents.any(), oddEvenMarketWhereWonOdd(), anyProducerId()) .get(); assertThat(market.getMarketDefinition().getNameTemplate(langB)) - .isEqualTo(oddEvenDescription().getName()); + .isEqualTo(oddEvenMarketDescription().getName()); } } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/OutcomeAssert.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/OutcomeAssert.java index fead20ee..006e2df7 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/OutcomeAssert.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/oddsentities/markets/OutcomeAssert.java @@ -3,10 +3,14 @@ */ package com.sportradar.unifiedodds.sdk.impl.oddsentities.markets; +import static com.sportradar.unifiedodds.sdk.impl.oddsentities.markets.ExpectationTowardsSdkErrorHandlingStrategy.WILL_THROW_EXCEPTIONS; import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.sportradar.unifiedodds.sdk.exceptions.NameGenerationException; import com.sportradar.unifiedodds.sdk.oddsentities.Outcome; import com.sportradar.utils.domain.names.TranslationHolder; +import java.util.Locale; import org.assertj.core.api.Assertions; public class OutcomeAssert extends AbstractOutcomeAssert { @@ -35,4 +39,44 @@ public OutcomeAssert hasNameInNonDefaultLanguage(TranslationHolder translation) .containsEntry(translation.getLanguage(), translation.getWord()); return this; } + + public OutcomeAssert getNameForDefault( + Locale aLanguage, + ExpectationTowardsSdkErrorHandlingStrategy errorHandling + ) { + if (errorHandling == WILL_THROW_EXCEPTIONS) { + resultsInMarketDescriptionNotFoundWhen(actual::getName); + resultsInMarketDescriptionNotFoundWhen(() -> actual.getName(aLanguage)); + resultsInMarketDescriptionNotFoundWhen(() -> actual.getNames(singletonList(aLanguage))); + } else { + Assertions.assertThat(actual.getName()).isNull(); + Assertions.assertThat(actual.getName(aLanguage)).isNull(); + Assertions.assertThat(actual.getNames(singletonList(aLanguage))).isNull(); + } + return this; + } + + public OutcomeAssert getNameForGiven( + Locale aLanguage, + ExpectationTowardsSdkErrorHandlingStrategy errorHandling + ) { + if (errorHandling == WILL_THROW_EXCEPTIONS) { + resultsInMarketDescriptionNotFoundWhen(() -> actual.getName(aLanguage)); + resultsInMarketDescriptionNotFoundWhen(() -> actual.getNames(singletonList(aLanguage))); + } else { + Assertions.assertThat(actual.getName(aLanguage)).isNull(); + Assertions.assertThat(actual.getNames(singletonList(aLanguage))).isNull(); + } + return this; + } + + private void resultsInMarketDescriptionNotFoundWhen(Runnable runnable) { + assertThatThrownBy(runnable::run) + .isInstanceOf(NameGenerationException.class) + .message() + .containsAnyOf( + "The name description parsing failed", + "The generation of name for flex score market outcome failed" + ); + } } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/rabbitconnection/ConnectionFixtureTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/rabbitconnection/ConnectionFixtureTest.java index 66e7b456..b83220f9 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/rabbitconnection/ConnectionFixtureTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/rabbitconnection/ConnectionFixtureTest.java @@ -3,6 +3,7 @@ */ package com.sportradar.unifiedodds.sdk.impl.rabbitconnection; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -69,6 +70,8 @@ public void connectionShouldNotBeOpenIfItWasClosedByApplication() throws IOExcep @Test public void closingConnectionWithoutRegisteredShutdownListenerShouldSucceed() { connection.getControlApi().closeInitiatedByRabbitMq(); + + assertFalse(connection.isOpen()); } @Test @@ -84,7 +87,10 @@ public void blockingConnectionShouldResultInCallingRegisteredListener() throws I @Test public void blockingConnectionWithoutRegisteredListenerShouldSucceed() throws IOException { - connection.getControlApi().blockDueTo("reason"); + assertThatNoException() + .isThrownBy(() -> { + connection.getControlApi().blockDueTo("reason"); + }); } @Test diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/rabbitconnection/RabbitMqChannelImplTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/rabbitconnection/RabbitMqChannelImplTest.java index 842a0922..70565e14 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/rabbitconnection/RabbitMqChannelImplTest.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/rabbitconnection/RabbitMqChannelImplTest.java @@ -11,6 +11,7 @@ import static com.sportradar.utils.time.TimeInterval.minutes; import static com.sportradar.utils.time.TimeInterval.seconds; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.junit.Assert.fail; import static org.mockito.Mockito.*; @@ -91,10 +92,11 @@ public void rabbitMqChannelShouldBeCreatedOnOpeningSupervisor() throws IOExcepti } @Test - public void rabbitMqChannelShouldNotBeCreatedIfConnectionWasUnavailableWhenOpeningSupervisor() - throws IOException { - new RabbitMqChannelSupervisors.Builder().with(mock(AmqpConnectionFactory.class)).opened(); - //ensuring no exception is thrown + public void rabbitMqChannelShouldNotBeCreatedIfConnectionWasUnavailableWhenOpeningSupervisor() { + assertThatNoException() + .isThrownBy(() -> { + new RabbitMqChannelSupervisors.Builder().with(mock(AmqpConnectionFactory.class)).opened(); + }); } } @@ -521,6 +523,9 @@ public String getConsumerDescription() { return null; } + @Override + public void close() throws IOException {} + public void verifyNoMessagesReceived() { assertThat(count).isZero(); } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/recovery/RecoveryManagerTests.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/recovery/RecoveryManagerTests.java index 14606ac3..a90f0199 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/recovery/RecoveryManagerTests.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/impl/recovery/RecoveryManagerTests.java @@ -4,6 +4,7 @@ package com.sportradar.unifiedodds.sdk.impl.recovery; +import static org.assertj.core.api.Assertions.assertThatNoException; import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -574,8 +575,11 @@ public void handleChannelRecoveryWhileProducerIsUpTest() { @Test public void recoveyStartedCallbacksShouldNotFail() { - recoveryManager.handleRecoveryStarted(mock(Recoverable.class)); - recoveryManager.handleTopologyRecoveryStarted(mock(Recoverable.class)); + assertThatNoException() + .isThrownBy(() -> { + recoveryManager.handleRecoveryStarted(mock(Recoverable.class)); + recoveryManager.handleTopologyRecoveryStarted(mock(Recoverable.class)); + }); } @Test diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/integrationtest/unifiedapi/DataRouterManagerImplRequestingScheduledSportEventIdsForTournamentIT.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/integrationtest/unifiedapi/DataRouterManagerImplRequestingScheduledSportEventIdsForTournamentIT.java index e10ae640..6da0169c 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/integrationtest/unifiedapi/DataRouterManagerImplRequestingScheduledSportEventIdsForTournamentIT.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/integrationtest/unifiedapi/DataRouterManagerImplRequestingScheduledSportEventIdsForTournamentIT.java @@ -138,7 +138,7 @@ private DataProvider tournamentSchedules() { configWithToken(), HttpClientBuilder.create().build(), mock(UnifiedOddsStatistics.class), - new HttpResponseHandler(deserializer), + new HttpResponseHandler(), mock(UserAgentProvider.class) ), deserializer diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/shared/FeedMessageBuilder.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/shared/FeedMessageBuilder.java index 753dfa21..529c5b19 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/shared/FeedMessageBuilder.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/shared/FeedMessageBuilder.java @@ -5,6 +5,7 @@ import com.sportradar.uf.datamodel.UfOddsChange; import com.sportradar.uf.datamodel.UfOddsChangeMarket; import com.sportradar.uf.datamodel.UfSnapshotComplete; +import com.sportradar.unifiedodds.sdk.conn.GlobalVariables; import com.sportradar.unifiedodds.sdk.conn.ProducerId; import com.sportradar.unifiedodds.sdk.conn.SportEvent; import java.util.Date; @@ -16,12 +17,10 @@ @SuppressWarnings({ "ParameterAssignment", "ClassDataAbstractionCoupling", "MagicNumber" }) public class FeedMessageBuilder { - private final ProducerId producerId; - private final SportEvent sportEvent; + private final GlobalVariables globalVariables; - public FeedMessageBuilder(ProducerId producerId, SportEvent sportEvent) { - this.producerId = producerId; - this.sportEvent = sportEvent; + public FeedMessageBuilder(GlobalVariables globalVariables) { + this.globalVariables = globalVariables; } /** @@ -39,7 +38,7 @@ public UfOddsChange buildOddsChange(Long eventId, Integer productId, Long reques UfOddsChange message = new UfOddsChange(); message.setEventId(eventId == null ? "sr:match:1000" : "sr:match:" + eventId); - message.setProduct(productId == null ? producerId.get() : productId); + message.setProduct(productId == null ? globalVariables.getProducer().get() : productId); message.setTimestamp(timestamp == null ? new Date().getTime() : timestamp.getTime()); message.setRequestId(requestId); @@ -50,8 +49,8 @@ public String oddsChange(UfOddsChangeMarket market) { UfOddsChange.UfOdds odds = new UfOddsChange.UfOdds(); odds.getMarket().add(market); val oddsChange = new UfOddsChange(); - oddsChange.setProduct(producerId.get()); - oddsChange.setEventId(sportEvent.getUrn().toString()); + oddsChange.setProduct(globalVariables.getProducer().get()); + oddsChange.setEventId(globalVariables.getSportEventUrn().getUrn().toString()); oddsChange.setTimestamp(new Date().getTime()); oddsChange.setOdds(odds); return Helper.serializeToJaxbXml(oddsChange); @@ -164,7 +163,7 @@ public UfBetStop buildBetStop(Long eventId, Integer productId, Long requestId, D UfBetStop message = new UfBetStop(); message.setEventId(eventId == null ? "sr:match:1000" : "sr:match:" + eventId); - message.setProduct(productId == null ? producerId.get() : productId); + message.setProduct(productId == null ? globalVariables.getProducer().get() : productId); message.setTimestamp(timestamp == null ? new Date().getTime() : timestamp.getTime()); message.setRequestId(requestId); @@ -180,7 +179,7 @@ public UfBetStop buildBetStop(Long eventId, Integer productId, Long requestId, D */ public UfAlive buildAlive(Integer productId, Date timestamp, boolean subscribed) { UfAlive message = new UfAlive(); - message.setProduct(productId == null ? producerId.get() : productId); + message.setProduct(productId == null ? globalVariables.getProducer().get() : productId); message.setTimestamp(timestamp == null ? new Date().getTime() : timestamp.getTime()); message.setSubscribed(subscribed ? 1 : 0); @@ -215,7 +214,7 @@ public UfAlive buildAlive(Integer productId) { */ public UfSnapshotComplete buildSnapshotComplete(Integer productId, long requestId, Date timestamp) { UfSnapshotComplete message = new UfSnapshotComplete(); - message.setProduct(productId == null ? producerId.get() : productId); + message.setProduct(productId == null ? globalVariables.getProducer().get() : productId); message.setTimestamp(timestamp == null ? new Date().getTime() : timestamp.getTime()); message.setRequestId(requestId); diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Conjunctions.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Conjunctions.java new file mode 100644 index 00000000..827cd3bf --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Conjunctions.java @@ -0,0 +1,11 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage; + +public class Conjunctions { + + public static T and(T object) { + return object; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Determiners.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Determiners.java new file mode 100644 index 00000000..96ab3b5a --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Determiners.java @@ -0,0 +1,11 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage; + +public class Determiners { + + public static T every(T object) { + return object; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Prepositions.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Prepositions.java new file mode 100644 index 00000000..c98701ac --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/generic/naturallanguage/Prepositions.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.testutil.generic.naturallanguage; + +public class Prepositions { + + public static T of(T object) { + return object; + } + + public static T from(T object) { + return object; + } + + public static T to(T object) { + return object; + } + + public static T by(T object) { + return object; + } + + public static T in(T object) { + return object; + } + + public static T with(T object) { + return object; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/utils/domain/config/Tokens.java b/sdk-core/src/test/java/com/sportradar/utils/domain/config/Tokens.java new file mode 100644 index 00000000..77cb6b40 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/utils/domain/config/Tokens.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.utils.domain.config; + +import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; + +public class Tokens { + + public static final int TOKEN_LENGTH = 18; + + public static String any() { + return randomAlphanumeric(TOKEN_LENGTH); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/utils/domain/specifiers/MarketSpecifiers.java b/sdk-core/src/test/java/com/sportradar/utils/domain/specifiers/MarketSpecifiers.java index 4ea43c0d..49413e40 100644 --- a/sdk-core/src/test/java/com/sportradar/utils/domain/specifiers/MarketSpecifiers.java +++ b/sdk-core/src/test/java/com/sportradar/utils/domain/specifiers/MarketSpecifiers.java @@ -4,8 +4,10 @@ package com.sportradar.utils.domain.specifiers; import static com.sportradar.utils.generic.testing.RandomObjectPicker.pickOneRandomlyFrom; -import static java.util.Collections.emptyMap; +import com.google.common.collect.ImmutableMap; +import com.sportradar.unifiedodds.sdk.conn.Identifiable; +import com.sportradar.unifiedodds.sdk.impl.UnifiedFeedConstants; import com.sportradar.unifiedodds.sdk.testutil.generic.collections.Maps; import java.util.Collections; import java.util.HashMap; @@ -23,4 +25,8 @@ public static Map anySpecifiers() { return pickOneRandomlyFrom(noSpecifiers, oneSpecifier, twoSpecifiers); } + + public static Map variant(Identifiable variant) { + return ImmutableMap.of(UnifiedFeedConstants.VARIANT_DESCRIPTION_NAME, variant.id()); + } } diff --git a/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomInteger.java b/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomInteger.java index b3d0455b..00a57b0c 100644 --- a/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomInteger.java +++ b/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomInteger.java @@ -10,4 +10,8 @@ private RandomInteger() {} public static int fromRangeInclusive(int min, int max) { return min + (int) (Math.random() * (max - min + 1)); } + + public static int fromRange(int min, int max) { + return min + (int) (Math.random() * (max - min)); + } } diff --git a/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomIntegerTest.java b/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomIntegerTest.java index 7139ef49..d24f8216 100644 --- a/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomIntegerTest.java +++ b/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomIntegerTest.java @@ -30,4 +30,21 @@ public void generatesValuesFromRangeInclusively() { assertThat(integers).hasSize(rangeInclusive); assertThat(integers).containsOnly(lowestValue, midValue, highestValue); } + + @Test + public void generatesValuesFromRange() { + final int lowestValue = 5; + final int midValue = 6; + final int highestValue = 7; + final int range = 3; + + List integers = Stream + .generate(() -> RandomInteger.fromRange(lowestValue, highestValue + 1)) + .limit(sampleSize) + .distinct() + .collect(Collectors.toList()); + + assertThat(integers).hasSize(range); + assertThat(integers).containsOnly(lowestValue, midValue, highestValue); + } } diff --git a/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomObjectPicker.java b/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomObjectPicker.java index 9a6cd19b..b134b55e 100644 --- a/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomObjectPicker.java +++ b/sdk-core/src/test/java/com/sportradar/utils/generic/testing/RandomObjectPicker.java @@ -3,11 +3,26 @@ */ package com.sportradar.utils.generic.testing; +import static com.sportradar.utils.generic.testing.RandomInteger.fromRange; +import static com.sportradar.utils.generic.testing.RandomInteger.fromRangeInclusive; + +import java.util.ArrayList; +import java.util.Collection; + public final class RandomObjectPicker { private RandomObjectPicker() {} public static T pickOneRandomlyFrom(T... objects) { - return objects[RandomInteger.fromRangeInclusive(0, objects.length - 1)]; + return objects[fromRangeInclusive(0, objects.length - 1)]; + } + + public static T pickOneRandomlyFrom(Collection objects) { + final int anyIndex = fromRange(0, objects.size()); + return asList(objects).get(anyIndex); + } + + private static ArrayList asList(Collection objects) { + return new ArrayList<>(objects); } } diff --git a/sdk-example/checkstyle/checkstyle.xml b/sdk-example/checkstyle/checkstyle.xml new file mode 100644 index 00000000..8473b57f --- /dev/null +++ b/sdk-example/checkstyle/checkstyle.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/sdk-example/pom.xml b/sdk-example/pom.xml index 012804ab..025e887b 100644 --- a/sdk-example/pom.xml +++ b/sdk-example/pom.xml @@ -5,22 +5,32 @@ 4.0.0 - - unified-feed-sdk-parent - com.sportradar.unifiedodds.sdk - 3.1.0 - com.sportradar.unifiedodds.sdk.example unified-feed-sdk-example + 3.3.0-SNAPSHOT Unified Odds Feed SDK - Examples and Tools + + 1.3.8 + + com.sportradar.unifiedodds.sdk unified-feed-sdk ${project.version} + + ch.qos.logback + logback-core + ${logback.version} + + + ch.qos.logback + logback-classic + ${logback.version} + @@ -37,6 +47,63 @@ + + com.hubspot.maven.plugins + prettier-maven-plugin + 0.18 + + 1.6.1 + 110 + 4 + false + true + true + + + + validate + + write + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.2.0 + + + com.puppycrawl.tools + checkstyle + 9.3 + + + + true + checkstyle/checkstyle.xml + UTF-8 + **/*.properties,**/*.xml + true + + + + test + + check + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + diff --git a/sdk-example/src/main/java/com/sportradar/unifiedodds/example/BasicUofSdkExampleMain.java b/sdk-example/src/main/java/com/sportradar/unifiedodds/example/BasicUofSdkExampleMain.java index f52b2e39..66b0cf29 100644 --- a/sdk-example/src/main/java/com/sportradar/unifiedodds/example/BasicUofSdkExampleMain.java +++ b/sdk-example/src/main/java/com/sportradar/unifiedodds/example/BasicUofSdkExampleMain.java @@ -28,13 +28,7 @@ public static void main(String[] args) throws InitException, IOException, Interr // - as a system property(JVM argument -Duf.accesstoken=) // and than invoking setAccessTokenFromSystemVar on the builder // - directly setting the access token in the builder using the setAccessToken(String accessToken) method - UofConfiguration config = UofSdk - .getUofConfigurationBuilder() - .setAccessToken("your-staging-token-here") - .selectEnvironment(Environment.GlobalIntegration) - .setNodeId(SdkConstants.NODE_ID) - .setDefaultLanguage(Locale.ENGLISH) - .build(); + UofConfiguration config = UofSdk.getUofConfigurationBuilder().buildConfigFromSdkProperties(); // create the new feed UofSdk uofSdk = new UofSdk(new GlobalEventsListener(), config); diff --git a/sdk-example/src/main/java/com/sportradar/unifiedodds/example/UofSdkExamples.java b/sdk-example/src/main/java/com/sportradar/unifiedodds/example/UofSdkExamples.java index 3ac99edf..0deffeec 100644 --- a/sdk-example/src/main/java/com/sportradar/unifiedodds/example/UofSdkExamples.java +++ b/sdk-example/src/main/java/com/sportradar/unifiedodds/example/UofSdkExamples.java @@ -61,12 +61,14 @@ private static void doExampleSelection() throws IOException, InitException, Inte String selection = getConsoleInput(); - System.out.println("Please enter a valid Unified Feed token for integration environment:"); + boolean isReplayEnvironment = selection.equals("7"); + String environment = isReplayEnvironment ? "Replay" : "Integration"; + System.out.println("Please enter a valid Unified Feed token for '" + environment + "' environment:"); String token = getConsoleInput(); switch (selection) { case "1": - SingleSessionSetup singleSessionSetup = new SingleSessionSetup(token); + SingleSessionSetup singleSessionSetup = new SingleSessionSetup(); singleSessionSetup.run(false); break; case "2": @@ -74,7 +76,7 @@ private static void doExampleSelection() throws IOException, InitException, Inte multiSessionSetup.run(); break; case "3": - SingleSessionSetup singleSessionSetupRecovery = new SingleSessionSetup(token); + SingleSessionSetup singleSessionSetupRecovery = new SingleSessionSetup(); singleSessionSetupRecovery.run(true); break; case "4": @@ -90,7 +92,7 @@ private static void doExampleSelection() throws IOException, InitException, Inte availableMarketsPrinterMappings.print(true); break; case "7": - ReplaySessionSetup replaySessionSetup = new ReplaySessionSetup(token); + ReplaySessionSetup replaySessionSetup = new ReplaySessionSetup(); replaySessionSetup.run(); break; case "8": diff --git a/sdk-example/src/main/java/com/sportradar/unifiedodds/example/examples/ReplaySessionSetup.java b/sdk-example/src/main/java/com/sportradar/unifiedodds/example/examples/ReplaySessionSetup.java index 563ef74a..a646aaa0 100644 --- a/sdk-example/src/main/java/com/sportradar/unifiedodds/example/examples/ReplaySessionSetup.java +++ b/sdk-example/src/main/java/com/sportradar/unifiedodds/example/examples/ReplaySessionSetup.java @@ -6,7 +6,6 @@ import com.sportradar.unifiedodds.example.common.GlobalEventsListener; import com.sportradar.unifiedodds.example.common.MessageListener; -import com.sportradar.unifiedodds.example.common.SdkConstants; import com.sportradar.unifiedodds.sdk.MessageInterest; import com.sportradar.unifiedodds.sdk.UofSdk; import com.sportradar.unifiedodds.sdk.UofSdkForReplay; @@ -15,7 +14,6 @@ import com.sportradar.unifiedodds.sdk.replay.ReplayManager; import com.sportradar.utils.Urn; import java.io.IOException; -import java.util.Locale; /** * A basic example demonstrating on how to start the SDK with a single session and perform replay operations @@ -25,16 +23,16 @@ public class ReplaySessionSetup { private final UofSdkForReplay oddsFeed; - public ReplaySessionSetup(String token) { + public ReplaySessionSetup() { logEntry("Running the UofSdk SDK Basic example - single session with replay server"); logEntry("Building the configuration using the provided token"); + UofConfiguration configuration = UofSdk .getUofConfigurationBuilder() - .setAccessToken(token) - .selectReplay() - .setNodeId(SdkConstants.NODE_ID) - .setDefaultLanguage(Locale.ENGLISH) + .setAccessTokenFromApplicationYaml() + .selectCustom() // needs to be present, it seems + .loadConfigFromApplicationYml() .build(); logEntry("Creating a new UofSdkForReplay instance"); diff --git a/sdk-example/src/main/java/com/sportradar/unifiedodds/example/examples/SingleSessionSetup.java b/sdk-example/src/main/java/com/sportradar/unifiedodds/example/examples/SingleSessionSetup.java index 2bd44238..0ae49429 100644 --- a/sdk-example/src/main/java/com/sportradar/unifiedodds/example/examples/SingleSessionSetup.java +++ b/sdk-example/src/main/java/com/sportradar/unifiedodds/example/examples/SingleSessionSetup.java @@ -6,15 +6,12 @@ import com.sportradar.unifiedodds.example.common.GlobalEventsListener; import com.sportradar.unifiedodds.example.common.MessageListener; -import com.sportradar.unifiedodds.example.common.SdkConstants; import com.sportradar.unifiedodds.sdk.MessageInterest; import com.sportradar.unifiedodds.sdk.ProducerManager; import com.sportradar.unifiedodds.sdk.UofSdk; -import com.sportradar.unifiedodds.sdk.cfg.Environment; import com.sportradar.unifiedodds.sdk.cfg.UofConfiguration; import com.sportradar.unifiedodds.sdk.exceptions.InitException; import java.io.IOException; -import java.util.Locale; import java.util.concurrent.TimeUnit; /** @@ -25,18 +22,14 @@ public class SingleSessionSetup { private final UofSdk uofSdk; - public SingleSessionSetup(String token) { + public SingleSessionSetup() { logEntry("Running the UofSdk SDK Basic example - single session"); logEntry("Building the configuration using the provided token"); - UofConfiguration configuration = UofSdk - .getUofConfigurationBuilder() - .setAccessToken(token) - .selectEnvironment(Environment.GlobalIntegration) - .setNodeId(SdkConstants.NODE_ID) - .setDefaultLanguage(Locale.ENGLISH) - .build(); + UofConfiguration configuration = UofSdk.getUofConfigurationBuilder().buildConfigFromSdkProperties(); + + logEntry(configuration.toString()); logEntry("Creating a new UofSdk instance"); uofSdk = new UofSdk(new GlobalEventsListener(), configuration); } diff --git a/sdk-example/src/main/resources/UFSdkConfiguration.properties.all b/sdk-example/src/main/resources/UFSdkConfiguration.properties.all index 936a2c2b..2bbefce8 100644 --- a/sdk-example/src/main/resources/UFSdkConfiguration.properties.all +++ b/sdk-example/src/main/resources/UFSdkConfiguration.properties.all @@ -1,15 +1,35 @@ # all the configuration properties are optional, for detailed description check the "ConfigurationAccessTokenSetter" +# ==== MANDATORY PROPERTIES ==== + # the access token provided by Sportradar uf.sdk.accessToken= # the default language (en) uf.sdk.defaultLanguage=en +# the node id which should be used by the SDK +uf.sdk.nodeId= + +# ==== COMMON PROPERTIES ==== + # the desired languages, separated with comma (es,fr) uf.sdk.desiredLanguages= -# the messaging host +# a list of producer identifiers that should be automatically disabled, separated with comma (1,2,4) +uf.sdk.disabledProducers= + +# the exception handling strategy that should be used trough the SDK (throw/catch) +uf.sdk.exceptionHandlingStrategy= + +# a value indicating to which unified feed environment sdk should connect (Environment enum value) +uf.sdk.environment= + +# ==== CUSTOM CONFIGURATION ==== + +# Normally, the following properties should not be set in production environment. + +# rabbit server connection host without scheme uf.sdk.messagingHost= # the messaging port @@ -18,32 +38,17 @@ uf.sdk.messagingPort= # an indication if the connection to the messaging broker should use ssl (true/false) uf.sdk.messagingUseSsl= -# the messaging username +# the messaging username (if left empty, it is replaced with access token) uf.sdk.messagingUsername= -# the messaging password uf.sdk.messagingPassword= -# the messaging virtual host +# the messaging virtual host (populated from whoami.xml endpoint) uf.sdk.messagingVirtualHost= -# the api host uf.sdk.apiHost= -# the api port uf.sdk.apiPort= # an indication if the request to the API should use ssl (true/false) -uf.sdk.apiUseSsl= - -# a list of producer identifiers that should be automatically disabled, separated with comma (1,2,4) -uf.sdk.disabledProducers= - -# the node id which should be used by the SDK -uf.sdk.nodeId= - -# the exception handling strategy that should be used trough the SDK (throw/catch) -uf.sdk.exceptionHandlingStrategy= - -# a value indicating to which unified feed environment sdk should connect (Environment enum value) -uf.sdk.environment= \ No newline at end of file +uf.sdk.apiUseSsl= \ No newline at end of file diff --git a/sdk-example/src/main/resources/application.yml.all b/sdk-example/src/main/resources/application.yml.all index 6c0287dd..a5f8046d 100644 --- a/sdk-example/src/main/resources/application.yml.all +++ b/sdk-example/src/main/resources/application.yml.all @@ -17,8 +17,7 @@ sportradar: exceptionHandlingStrategy: # a value indicating to which unified feed environment sdk should connect # (Integration, Production, Custom, Replay, - # GlobalReplay, GlobalProduction, GlobalIntegration, - # ProxySingapore, ProxyTokyo) + # GlobalReplay, GlobalProduction, GlobalIntegration) environment: # message broker related properties - only used when setting Custom environment