diff --git a/.gitignore b/.gitignore index eb2b61ea..df9656e1 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ buildNumber.properties #custom exclusions /uf-sdk-logs/ +**/uf-sdk-logs/ UFSdkConfiguration.properties application.yml @@ -77,4 +78,3 @@ application.yml # logs *.log.gz -*.log \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index aed91f9c..15aa11d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ### Unified Feed SDK 2.x changelog +**2.0.61 (2023-07-20)** +* Stages can be of SprintRace type + **2.0.60.0 (2023-05-17)** * CustomBetManager respects ExceptionHandlingStrategy for all non-argument-validating exceptions * CustomBetManager now throws CommunicationException on API failures diff --git a/pom.xml b/pom.xml index ce896e3e..18a67a63 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ 4.0.0 com.sportradar.unifiedodds.sdk unified-feed-sdk-parent - 2.0.60 + 2.0.61 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. diff --git a/sdk-core/pom.xml b/sdk-core/pom.xml index a5b7d7b1..fc2dbba3 100644 --- a/sdk-core/pom.xml +++ b/sdk-core/pom.xml @@ -8,7 +8,7 @@ com.sportradar.unifiedodds.sdk unified-feed-sdk - 2.0.60 + 2.0.61 Unified Odds Feed SDK diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/exportable/ExportableRaceStageCI.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/exportable/ExportableRaceStageCI.java index fabfac02..798eeb22 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/exportable/ExportableRaceStageCI.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/caching/exportable/ExportableRaceStageCI.java @@ -12,7 +12,7 @@ import java.util.Locale; import java.util.Map; -@SuppressWarnings({ "AbbreviationAsWordInName", "HiddenField", "ParameterNumber" }) +@SuppressWarnings({ "HiddenField", "ParameterNumber", "AbbreviationAsWordInName" }) public class ExportableRaceStageCI extends ExportableStageCI { private Locale defaultLocale; diff --git a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/entities/StageType.java b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/entities/StageType.java index 4ac0304a..00da287d 100644 --- a/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/entities/StageType.java +++ b/sdk-core/src/main/java/com/sportradar/unifiedodds/sdk/entities/StageType.java @@ -24,6 +24,7 @@ public enum StageType { Qualifying, QualifyingPart, Lap, + SprintRace, Run; public static StageType mapFromApiValue(String str) { @@ -64,6 +65,8 @@ public static StageType mapFromApiValue(String str) { return Lap; case "run": return Run; + case "sprint_race": + return SprintRace; default: return null; } diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ErrorHandlingStrategiesTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ErrorHandlingStrategiesTest.java new file mode 100644 index 00000000..67da91ca --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ErrorHandlingStrategiesTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.Test; + +public class ErrorHandlingStrategiesTest { + + private final int sampleSize = 100; + private List strategies = Stream + .generate(() -> ExceptionHandlingStrategies.anyErrorHandlingStrategy()) + .limit(sampleSize) + .distinct() + .collect(Collectors.toList()); + + @Test + public void anyStrategyGeneratesNotAlwaysTheSameStrategy() { + assertThat(strategies).hasSizeGreaterThan(1); + } + + @Test + public void anyStrategyDoesNotGenerateNulls() { + assertThat(strategies).doesNotContainNull(); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ExceptionHandlingStrategies.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ExceptionHandlingStrategies.java new file mode 100644 index 00000000..53d6d62f --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/ExceptionHandlingStrategies.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk; + +import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Catch; +import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Throw; + +import java.util.Arrays; +import java.util.Random; +import lombok.val; + +public class ExceptionHandlingStrategies { + + private static Random random = new Random(); + + private ExceptionHandlingStrategies() {} + + public static ExceptionHandlingStrategy anyErrorHandlingStrategy() { + val strategiesPool = Arrays.asList(Throw, Catch); + return strategiesPool.get(random.nextInt(strategiesPool.size())); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/DataRouterManagers.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/DataRouterManagers.java new file mode 100644 index 00000000..7cfb84d3 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/DataRouterManagers.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.impl; + +import com.sportradar.unifiedodds.sdk.caching.DataRouterManager; + +public class DataRouterManagers { + + private DataRouterManagers() {} + + public static DataRouterManager any() { + return new NoOpDataRouterManager(); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/NoOpDataRouterManager.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/NoOpDataRouterManager.java new file mode 100644 index 00000000..51f07f03 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/NoOpDataRouterManager.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.impl; + +import com.sportradar.uf.sportsapi.datamodel.SAPIMatchTimelineEndpoint; +import com.sportradar.unifiedodds.sdk.caching.CacheItem; +import com.sportradar.unifiedodds.sdk.caching.DataRouterManager; +import com.sportradar.unifiedodds.sdk.custombetentities.AvailableSelections; +import com.sportradar.unifiedodds.sdk.custombetentities.Calculation; +import com.sportradar.unifiedodds.sdk.custombetentities.CalculationFilter; +import com.sportradar.unifiedodds.sdk.custombetentities.Selection; +import com.sportradar.unifiedodds.sdk.entities.FixtureChange; +import com.sportradar.unifiedodds.sdk.entities.PeriodStatus; +import com.sportradar.unifiedodds.sdk.entities.ResultChange; +import com.sportradar.unifiedodds.sdk.exceptions.internal.CommunicationException; +import com.sportradar.utils.URN; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class NoOpDataRouterManager implements DataRouterManager { + + @Override + public void requestSummaryEndpoint(Locale locale, URN id, CacheItem requester) + throws CommunicationException {} + + @Override + public void requestFixtureEndpoint(Locale locale, URN id, boolean useCachedProvider, CacheItem requester) + throws CommunicationException {} + + @Override + public void requestDrawSummary(Locale locale, URN id, CacheItem requester) + throws CommunicationException {} + + @Override + public void requestDrawFixture(Locale locale, URN id, CacheItem requester) + throws CommunicationException {} + + @Override + public void requestAllTournamentsForAllSportsEndpoint(Locale locale) throws CommunicationException {} + + @Override + public void requestAllSportsEndpoint(Locale locale) throws CommunicationException {} + + @Override + public List requestAllLotteriesEndpoint(Locale locale, Boolean requireResult) + throws CommunicationException { + return null; + } + + @Override + public List requestEventsFor(Locale locale, URN tournamentId) throws CommunicationException { + return null; + } + + @Override + public List requestEventsFor(Locale locale, Date date) throws CommunicationException { + return null; + } + + @Override + public List requestLotterySchedule(Locale locale, URN lotteryId, CacheItem requester) + throws CommunicationException { + return null; + } + + @Override + public void requestPlayerProfileEndpoint(Locale locale, URN id, CacheItem requester) + throws CommunicationException {} + + @Override + public void requestCompetitorEndpoint(Locale locale, URN id, CacheItem requester) + throws CommunicationException {} + + @Override + public void requestSimpleTeamEndpoint(Locale locale, URN id, CacheItem requester) + throws CommunicationException {} + + @Override + public List requestSeasonsFor(Locale locale, URN tournamentId) throws CommunicationException { + return null; + } + + @Override + public SAPIMatchTimelineEndpoint requestEventTimelineEndpoint(Locale locale, URN id, CacheItem requester) + throws CommunicationException { + return null; + } + + @Override + public void requestSportCategoriesEndpoint(Locale locale, URN id, CacheItem requester) + throws CommunicationException {} + + @Override + public AvailableSelections requestAvailableSelections(URN id) throws CommunicationException { + return null; + } + + @Override + public Calculation requestCalculateProbability(List selections) throws CommunicationException { + return null; + } + + @Override + public CalculationFilter requestCalculateProbabilityFilter(List selections) + throws CommunicationException { + return null; + } + + @Override + public List requestFixtureChanges(Date after, URN sportId, Locale locale) + throws CommunicationException { + return null; + } + + @Override + public List requestResultChanges(Date after, URN sportId, Locale locale) + throws CommunicationException { + return null; + } + + @Override + public List requestListSportEvents(Locale locale, int startIndex, int limit) + throws CommunicationException { + return null; + } + + @Override + public List requestAvailableTournamentsFor(Locale locale, URN sportId) + throws CommunicationException { + return null; + } + + @Override + public List requestPeriodSummary( + URN id, + Locale locale, + List competitorIds, + List periods + ) throws CommunicationException { + return null; + } + + @Override + public void close() {} +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/ci/RaceStageCiImplStageTypeTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/ci/RaceStageCiImplStageTypeTest.java new file mode 100644 index 00000000..cbddf0d0 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/ci/RaceStageCiImplStageTypeTest.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.impl.ci; + +import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Catch; +import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy.Throw; +import static com.sportradar.unifiedodds.sdk.caching.impl.ci.RaceStageCiToMergeTo.into; +import static com.sportradar.unifiedodds.sdk.caching.impl.ci.RaceStageCis.exportSerializeAndUseConstructorToReimport; +import static com.sportradar.unifiedodds.sdk.caching.impl.ci.RaceStageCis.usingConstructor; +import static com.sportradar.unifiedodds.sdk.entities.StageType.SprintRace; +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.*; +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.caching.DataRouterManager; +import com.sportradar.unifiedodds.sdk.exceptions.internal.CommunicationException; +import com.sportradar.unifiedodds.sdk.testutil.javautil.Languages; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import lombok.val; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.mockito.stubbing.Stubber; + +@RunWith(Enclosed.class) +public class RaceStageCiImplStageTypeTest { + + private static final String SAPI_SPRINT_RACE = "sprint_race"; + + private static SAPIStageSummaryEndpoint stageSummaryOfType(String type) { + val stageSummary = new SAPIStageSummaryEndpoint(); + SAPISportEvent sportEvent = new SAPISportEvent(); + sportEvent.setStageType(type); + stageSummary.setSportEvent(sportEvent); + return stageSummary; + } + + public static class WhenStageTypeIsProvidedOnConstruction { + + @Test + public void fromSportEventItPreservesStageType() { + SAPISportEvent stage = new SAPISportEvent(); + stage.setStageType(SAPI_SPRINT_RACE); + val raceStage = usingConstructor().constructFrom(stage); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + @Test + public void fromFixtureItPreserveStageType() { + val stage = new SAPIFixture(); + stage.setStageType(SAPI_SPRINT_RACE); + val raceStage = usingConstructor().constructFrom(stage); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + @Test + public void fromStageSummaryItPreserveStageType() { + val stage = new SAPIStageSummaryEndpoint(); + SAPISportEvent stage1 = new SAPISportEvent(); + stage1.setStageType(SAPI_SPRINT_RACE); + stage.setSportEvent(stage1); + val raceStage = usingConstructor().constructFrom(stage); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + @Test + public void fromChildSportEventItPreserveStageType() { + val stage = new SAPISportEventChildren.SAPISportEvent(); + stage.setStageType(SAPI_SPRINT_RACE); + val raceStage = usingConstructor().constructFrom(stage); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + @Test + public void fromParentStageItPreserveStageType() { + val sprintStage = new SAPIParentStage(); + sprintStage.setStageType(SAPI_SPRINT_RACE); + val raceStage = usingConstructor().constructFrom(sprintStage); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + @Test + public void inFormOfImportingExportedStageCiItPreserveStageType() throws Exception { + val stage = new SAPIParentStage(); + stage.setStageType(SAPI_SPRINT_RACE); + val raceStage = usingConstructor().constructFrom(stage); + val importedRaceStage = exportSerializeAndUseConstructorToReimport(raceStage).construct(); + + assertThat(importedRaceStage.getStageType()).isEqualTo(SprintRace); + } + } + + @RunWith(Enclosed.class) + public static class GivenStageTypeIsNotAvailableOnConstruction { + + @RunWith(JUnitParamsRunner.class) + public static class ButSubsequentMergeContainsStageType { + + @Test + @Parameters(method = "raceStagesWithoutStageType") + public void whenSubsequentMergeObjectIsSportEvent(RaceStageCIImpl raceStage) { + val updatedStage = new SAPISportEvent(); + updatedStage.setStageType(SAPI_SPRINT_RACE); + + raceStage.merge(updatedStage, Languages.any()); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + @Test + @Parameters(method = "raceStagesWithoutStageType") + public void whenSubsequentMergeObjectIsFixture(RaceStageCIImpl raceStage) { + val updatedStage = new SAPIFixture(); + updatedStage.setStageType(SAPI_SPRINT_RACE); + + raceStage.merge(updatedStage, Languages.any()); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + @Test + @Parameters(method = "raceStagesWithoutStageType") + public void whenSubsequentMergeObjectIsStageSummary(RaceStageCIImpl raceStage) { + val updatedStage = new SAPIStageSummaryEndpoint(); + SAPISportEvent stage = new SAPISportEvent(); + stage.setStageType(SAPI_SPRINT_RACE); + updatedStage.setSportEvent(stage); + + raceStage.merge(updatedStage, Languages.any()); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + @Test + @Parameters(method = "raceStagesWithoutStageType") + public void whenSubsequentMergeObjectIsChildSportEvent(RaceStageCIImpl raceStage) { + val updatedStage = new SAPISportEventChildren.SAPISportEvent(); + updatedStage.setStageType(SAPI_SPRINT_RACE); + + raceStage.merge(updatedStage, Languages.any()); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + @Test + @Parameters(method = "raceStagesWithoutStageType") + public void whenSubsequentMergeObjectIsParentStage(RaceStageCIImpl raceStage) { + val updatedStage = new SAPIParentStage(); + updatedStage.setStageType(SAPI_SPRINT_RACE); + + raceStage.merge(updatedStage, Languages.any()); + + assertThat(raceStage.getStageType()).isEqualTo(SprintRace); + } + + private Object[] raceStagesWithoutStageType() throws Exception { + return new Object[][] { + { usingConstructor().constructFrom(new SAPISportEvent()) }, + { usingConstructor().constructFrom(new SAPIFixture()) }, + { usingConstructor().constructFrom(createEmptySapiStageSummary()) }, + { usingConstructor().constructFrom(new SAPISportEventChildren.SAPISportEvent()) }, + { usingConstructor().constructFrom(new SAPIParentStage()) }, + { createReimportedStage() }, + }; + } + + private static RaceStageCIImpl createReimportedStage() throws Exception { + SAPIParentStage anySapiObject = new SAPIParentStage(); + val stage = usingConstructor().constructFrom(anySapiObject); + return exportSerializeAndUseConstructorToReimport(stage).construct(); + } + + private static SAPIStageSummaryEndpoint createEmptySapiStageSummary() { + val sapiStageSummary = new SAPIStageSummaryEndpoint(); + sapiStageSummary.setSportEvent(new SAPISportEvent()); + return sapiStageSummary; + } + } + + public static class FromSummarySourceObject { + + @Test + public void thenSummaryIsNotReQueriedAsItIsConsideredToBeSourceOfTruthForStageType() + throws CommunicationException { + val stageSummary = new SAPIStageSummaryEndpoint(); + stageSummary.setSportEvent(new SAPISportEvent()); + DataRouterManager dataRouterManager = mock(DataRouterManager.class); + val stage = usingConstructor().with(dataRouterManager).constructFrom(stageSummary); + + stage.getStageType(); + + verify(dataRouterManager, never()).requestSummaryEndpoint(any(), any(), any()); + } + } + + @RunWith(Enclosed.class) + public static class FromNonSummarySourceObject { + + public static class ThenSummaryIsQueriedButDueToSomeEdgeCaseDownstreamItIsNotMergedIntoCi { + + private DataRouterManager dataRouterManager = mock(DataRouterManager.class); + + @Test + public void whenSourceObjectForConstructionIsSportEvent() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .constructFrom(new SAPISportEvent()); + + val stageType = stage.getStageType(); + + assertThat(stageType).isNull(); + verify(dataRouterManager).requestSummaryEndpoint(any(), any(), any()); + } + + @Test + public void whenSourceObjectForConstructionIsFixture() throws CommunicationException { + val stage = usingConstructor().with(dataRouterManager).constructFrom(new SAPIFixture()); + + val stageType = stage.getStageType(); + + assertThat(stageType).isNull(); + verify(dataRouterManager).requestSummaryEndpoint(any(), any(), any()); + } + + @Test + public void whenSourceObjectForConstructionIsChildSportEvent() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .constructFrom(new SAPISportEventChildren.SAPISportEvent()); + + val stageType = stage.getStageType(); + + assertThat(stageType).isNull(); + verify(dataRouterManager).requestSummaryEndpoint(any(), any(), any()); + } + + @Test + public void whenSourceObjectForConstructionIsParentStage() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .constructFrom(new SAPIParentStage()); + + val stageType = stage.getStageType(); + + assertThat(stageType).isNull(); + verify(dataRouterManager).requestSummaryEndpoint(any(), any(), any()); + } + + @Test + public void afterReimportingStageCi() throws Exception { + val stageWithoutType = usingConstructor().constructFrom(new SAPIParentStage()); + val importedRaceStage = exportSerializeAndUseConstructorToReimport(stageWithoutType) + .with(dataRouterManager) + .construct(); + + val stageType = importedRaceStage.getStageType(); + + assertThat(stageType).isNull(); + verify(dataRouterManager).requestSummaryEndpoint(any(), any(), any()); + } + } + + public static class ThenSummaryIsQueriedAndMergedIntoCiWithStageTypeUpdated { + + private DataRouterManager dataRouterManager = mock(DataRouterManager.class); + + @Test + public void whenSourceObjectForConstructionIsSportEvent() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .constructFrom(new SAPISportEvent()); + whenRequestedSummaryThenMerges(stageSummaryOfType(SAPI_SPRINT_RACE), into(stage)); + + assertThat(stage.getStageType()).isEqualTo(SprintRace); + } + + @Test + public void whenSourceObjectForConstructionIsFixture() throws CommunicationException { + val stage = usingConstructor().with(dataRouterManager).constructFrom(new SAPIFixture()); + whenRequestedSummaryThenMerges(stageSummaryOfType(SAPI_SPRINT_RACE), into(stage)); + + assertThat(stage.getStageType()).isEqualTo(SprintRace); + } + + @Test + public void whenSourceObjectForConstructionIsChildSportEvent() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .constructFrom(new SAPISportEventChildren.SAPISportEvent()); + whenRequestedSummaryThenMerges(stageSummaryOfType(SAPI_SPRINT_RACE), into(stage)); + + assertThat(stage.getStageType()).isEqualTo(SprintRace); + } + + @Test + public void whenSourceObjectForConstructionIsParentStage() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .constructFrom(new SAPIParentStage()); + whenRequestedSummaryThenMerges(stageSummaryOfType(SAPI_SPRINT_RACE), into(stage)); + + assertThat(stage.getStageType()).isEqualTo(SprintRace); + } + + @Test + public void afterReimportingStageCi() throws Exception { + SAPIParentStage anySourceObject = new SAPIParentStage(); + val stage = usingConstructor().constructFrom(anySourceObject); + val importedRaceStage = exportSerializeAndUseConstructorToReimport(stage) + .with(dataRouterManager) + .construct(); + whenRequestedSummaryThenMerges( + stageSummaryOfType(SAPI_SPRINT_RACE), + into(importedRaceStage) + ); + + assertThat(importedRaceStage.getStageType()).isEqualTo(SprintRace); + } + + private void whenRequestedSummaryThenMerges( + SAPIStageSummaryEndpoint raceStageSummary, + RaceStageCiToMergeTo stage + ) throws CommunicationException { + does(() -> stage.getValue().merge(raceStageSummary, Languages.any())) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + } + + private static Stubber does(Runnable action) { + return doAnswer(args -> { + action.run(); + return null; + }); + } + } + + @RunWith(Enclosed.class) + public static class AndSummaryIsQueriedButItFailsDueToCommunicationErrorAndSdkIsConfiguredToThrowErrors { + + public static class ThenErrorIsWrappedAndRethrown { + + private DataRouterManager dataRouterManager = mock(DataRouterManager.class); + private CommunicationException communicationError = new CommunicationException( + "any error message", + "anyUrl" + ); + private ExceptionHandlingStrategy sdkThrowsErrors = Throw; + + @Test + public void whenSourceObjectForConstructionIsSportEvent() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .with(sdkThrowsErrors) + .constructFrom(new SAPISportEvent()); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThatThrownBy(() -> stage.getStageType()).hasRootCause(communicationError); + } + + @Test + public void whenSourceObjectForConstructionIsFixture() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .with(sdkThrowsErrors) + .constructFrom(new SAPIFixture()); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThatThrownBy(() -> stage.getStageType()).hasRootCause(communicationError); + } + + @Test + public void whenSourceObjectForConstructionIsChildSportEvent() + throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .with(sdkThrowsErrors) + .constructFrom(new SAPISportEventChildren.SAPISportEvent()); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThatThrownBy(() -> stage.getStageType()).hasRootCause(communicationError); + } + + @Test + public void whenSourceObjectForConstructionIsParentStage() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .with(sdkThrowsErrors) + .constructFrom(new SAPIParentStage()); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThatThrownBy(() -> stage.getStageType()).hasRootCause(communicationError); + } + + @Test + public void afterReimportingStageCi() throws Exception { + val anySourceObject = new SAPIParentStage(); + val stage = usingConstructor().constructFrom(anySourceObject); + val importedStage = exportSerializeAndUseConstructorToReimport(stage) + .with(dataRouterManager) + .with(sdkThrowsErrors) + .construct(); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThatThrownBy(() -> importedStage.getStageType()) + .hasRootCause(communicationError); + } + } + } + + @RunWith(Enclosed.class) + public static class AndSummaryIsQueriedButItFailsDueToCommunicationErrorAndSdkIsConfiguredToCatchErrors { + + public static class ThenSdkDoesNotProvideStageType { + + private DataRouterManager dataRouterManager = mock(DataRouterManager.class); + private CommunicationException communicationError = new CommunicationException( + "any error message", + "anyUrl" + ); + private ExceptionHandlingStrategy sdkCatchesErrors = Catch; + + @Test + public void whenSourceObjectForConstructionIsSportEvent() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .with(sdkCatchesErrors) + .constructFrom(new SAPISportEvent()); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThat(stage.getStageType()).isNull(); + } + + @Test + public void whenSourceObjectForConstructionIsFixture() throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .with(sdkCatchesErrors) + .constructFrom(new SAPIFixture()); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThat(stage.getStageType()).isNull(); + } + + @Test + public void whenSourceObjectForConstructionIsChildSportEvent() + throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .with(sdkCatchesErrors) + .constructFrom(new SAPISportEventChildren.SAPISportEvent()); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThat(stage.getStageType()).isNull(); + } + + @Test + public void whenSourceObjectUserForConstructionIsParentStage() + throws CommunicationException { + val stage = usingConstructor() + .with(dataRouterManager) + .with(sdkCatchesErrors) + .constructFrom(new SAPIParentStage()); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThat(stage.getStageType()).isNull(); + } + + @Test + public void afterReimportingStageCi() throws Exception { + val anySapiSourceObject = new SAPIParentStage(); + val stage = usingConstructor().constructFrom(anySapiSourceObject); + val importedStage = exportSerializeAndUseConstructorToReimport(stage) + .with(dataRouterManager) + .with(sdkCatchesErrors) + .construct(); + doThrow(communicationError) + .when(dataRouterManager) + .requestSummaryEndpoint(any(), any(), any()); + + assertThat(importedStage.getStageType()).isNull(); + } + } + } + } + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/ci/RaceStageCiToMergeTo.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/ci/RaceStageCiToMergeTo.java new file mode 100644 index 00000000..b72f41a0 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/ci/RaceStageCiToMergeTo.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.impl.ci; + +import com.sportradar.unifiedodds.sdk.caching.StageCI; + +class RaceStageCiToMergeTo { + + private StageCI value; + + private RaceStageCiToMergeTo(StageCI value) { + this.value = value; + } + + static RaceStageCiToMergeTo into(StageCI target) { + return new RaceStageCiToMergeTo(target); + } + + StageCI getValue() { + return value; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/ci/RaceStageCis.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/ci/RaceStageCis.java new file mode 100644 index 00000000..47137643 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/caching/impl/ci/RaceStageCis.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.caching.impl.ci; + +import static com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategies.anyErrorHandlingStrategy; +import static com.sportradar.unifiedodds.sdk.testutil.serialization.JavaSerializer.deserialize; +import static com.sportradar.unifiedodds.sdk.testutil.serialization.JavaSerializer.serialize; +import static com.sportradar.utils.Urns.SportEvents.urnForAnyStage; +import static java.util.Optional.empty; + +import com.sportradar.uf.sportsapi.datamodel.*; +import com.sportradar.unifiedodds.sdk.ExceptionHandlingStrategy; +import com.sportradar.unifiedodds.sdk.caching.DataRouterManager; +import com.sportradar.unifiedodds.sdk.caching.exportable.ExportableRaceStageCI; +import com.sportradar.unifiedodds.sdk.caching.impl.DataRouterManagers; +import com.sportradar.unifiedodds.sdk.testutil.guava.libraryfixtures.Caches; +import com.sportradar.unifiedodds.sdk.testutil.javautil.Languages; +import java.util.*; +import lombok.val; + +public class RaceStageCis { + + private static Random random = new Random(); + + private RaceStageCis() {} + + public static BuilderForConstructionOnly usingConstructor() { + return new BuilderForConstructionOnly(); + } + + public static BuilderForConstructionFromExportedOnly usingConstructorToReimport( + ExportableRaceStageCI exported + ) { + return new BuilderForConstructionFromExportedOnly(exported); + } + + public static BuilderForConstructionFromExportedOnly exportSerializeAndUseConstructorToReimport( + RaceStageCIImpl original + ) throws Exception { + val exportedRaceStage = original.export(); + val serialized = serialize(exportedRaceStage); + val deserialized = deserialize(serialized); + return usingConstructorToReimport((ExportableRaceStageCI) deserialized); + } + + public static class BuilderForConstructionOnly { + + private Optional providedDataRouterManager = empty(); + private Optional providedErrorHandlingStrategy = empty(); + + private BuilderForConstructionOnly() {} + + public BuilderForConstructionOnly with(DataRouterManager dataRouterManager) { + this.providedDataRouterManager = Optional.of(dataRouterManager); + return this; + } + + public BuilderForConstructionOnly with(ExceptionHandlingStrategy exceptionHandlingStrategy) { + this.providedErrorHandlingStrategy = Optional.of(exceptionHandlingStrategy); + return this; + } + + public RaceStageCIImpl constructFrom(SAPISportEvent sapiSourceObject) { + return new RaceStageCIImpl( + urnForAnyStage(), + providedDataRouterManager.orElse(DataRouterManagers.any()), + Languages.any(), + providedErrorHandlingStrategy.orElse(anyErrorHandlingStrategy()), + sapiSourceObject, + Languages.any(), + Caches.any() + ); + } + + public RaceStageCIImpl constructFrom(SAPIFixture sapiSourceObject) { + return new RaceStageCIImpl( + urnForAnyStage(), + providedDataRouterManager.orElse(DataRouterManagers.any()), + Languages.any(), + providedErrorHandlingStrategy.orElse(anyErrorHandlingStrategy()), + sapiSourceObject, + Languages.any(), + Caches.any() + ); + } + + public RaceStageCIImpl constructFrom(SAPIStageSummaryEndpoint sapiSourceObject) { + return new RaceStageCIImpl( + urnForAnyStage(), + providedDataRouterManager.orElse(DataRouterManagers.any()), + Languages.any(), + providedErrorHandlingStrategy.orElse(anyErrorHandlingStrategy()), + sapiSourceObject, + Languages.any(), + Caches.any() + ); + } + + public RaceStageCIImpl constructFrom(SAPISportEventChildren.SAPISportEvent sapiSourceObject) { + return new RaceStageCIImpl( + urnForAnyStage(), + providedDataRouterManager.orElse(DataRouterManagers.any()), + Languages.any(), + providedErrorHandlingStrategy.orElse(anyErrorHandlingStrategy()), + sapiSourceObject, + Languages.any(), + Caches.any() + ); + } + + public RaceStageCIImpl constructFrom(SAPIParentStage sapiSourceObject) { + return new RaceStageCIImpl( + urnForAnyStage(), + providedDataRouterManager.orElse(DataRouterManagers.any()), + Languages.any(), + providedErrorHandlingStrategy.orElse(anyErrorHandlingStrategy()), + sapiSourceObject, + Languages.any(), + Caches.any() + ); + } + } + + public static class BuilderForConstructionFromExportedOnly { + + private ExportableRaceStageCI exportableToImport; + private Optional providedDataRouterManager = empty(); + private Optional providedErrorHandlingStrategy = empty(); + + private BuilderForConstructionFromExportedOnly(ExportableRaceStageCI exportableToImport) { + this.exportableToImport = exportableToImport; + } + + public BuilderForConstructionFromExportedOnly with(DataRouterManager dataRouterManager) { + this.providedDataRouterManager = Optional.of(dataRouterManager); + return this; + } + + public BuilderForConstructionFromExportedOnly with( + ExceptionHandlingStrategy exceptionHandlingStrategy + ) { + this.providedErrorHandlingStrategy = Optional.of(exceptionHandlingStrategy); + return this; + } + + public RaceStageCIImpl construct() { + return new RaceStageCIImpl( + exportableToImport, + providedDataRouterManager.orElse(DataRouterManagers.any()), + providedErrorHandlingStrategy.orElse(anyErrorHandlingStrategy()), + Caches.any() + ); + } + } +} 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 aff0dfd1..c6fbd865 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 @@ -19,6 +19,7 @@ import com.sportradar.unifiedodds.sdk.shared.TestProducersProvider; import com.sportradar.unifiedodds.sdk.testutil.rabbit.integration.*; import com.sportradar.utils.URN; +import java.io.IOException; import java.util.*; import java.util.concurrent.*; import lombok.val; @@ -94,16 +95,18 @@ public void start() { ManagementClient.closeConnection(connectionInfo.getName(), "cleanup"); } + val vhostLocation = VhostLocation.at(Constants.RABBIT_IP, Constants.UF_VIRTUALHOST); + val exchangeLocation = ExchangeLocation.at(vhostLocation, Constants.UF_EXCHANGE); + val adminCredentials = Credentials.with(Constants.ADMIN_USERNAME, Constants.ADMIN_PASSWORD); + val factory = new ConnectionFactory(); + try { - val vhostLocation = VhostLocation.at(Constants.RABBIT_IP, Constants.UF_VIRTUALHOST); - val exchangeLocation = ExchangeLocation.at(vhostLocation, Constants.UF_EXCHANGE); - val adminCredentials = Credentials.with(Constants.ADMIN_USERNAME, Constants.ADMIN_PASSWORD); - val factory = new ConnectionFactory(); producer = connectDeclaringExchange(exchangeLocation, adminCredentials, factory, time); - } catch (Exception ex) { - Helper.writeToOutput(ex.getMessage()); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (TimeoutException e) { + throw new RuntimeException(e); } - timerTaskScheduler = Executors.newScheduledThreadPool(1); timerFuture = timerTaskScheduler.scheduleAtFixedRate(() -> timerCheckAndSend(), 1, 1, TimeUnit.SECONDS); @@ -154,16 +157,16 @@ public void send(UnmarshalledMessage message, String routingKey, long timestamp) when(time.now()).thenReturn(timestamp); try { producer.send(msgBody, routingKey); - String result = String.format( - "Generated:%s, Routing: %s, Msg: %s", - new Date(timestamp), - routingKey, - message - ); - Helper.writeToOutput(result); - } catch (Exception ex) { - Helper.writeToOutput("Error sending message: " + ex.getMessage()); + } catch (IOException e) { + throw new RuntimeException(e); } + String result = String.format( + "Generated:%s, Routing: %s, Msg: %s", + new Date(timestamp), + routingKey, + message + ); + Helper.writeToOutput(result); } /** diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkConnectionTests.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkConnectionTests.java index 1042b21c..1b6cd74b 100644 --- a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkConnectionTests.java +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/conn/SdkConnectionTests.java @@ -426,7 +426,7 @@ public void multipleProducersWithAfterTest() throws InitException { new TestHttpHelper.UrlReplacement("/liveodds/", 1, HttpStatus.SC_ACCEPTED) ); feed.TestHttpHelper.PostResponses.add( - new TestHttpHelper.UrlReplacement("/prematch/", 1, HttpStatus.SC_ACCEPTED) + new TestHttpHelper.UrlReplacement("/pre/", 1, HttpStatus.SC_ACCEPTED) ); List disabledProducers = Arrays.asList(2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17); @@ -614,7 +614,7 @@ public void multipleProducersMultiSessionTest() throws InitException { new TestHttpHelper.UrlReplacement("/liveodds/", 1, HttpStatus.SC_ACCEPTED) ); feed.TestHttpHelper.PostResponses.add( - new TestHttpHelper.UrlReplacement("/prematch/", 1, HttpStatus.SC_ACCEPTED) + new TestHttpHelper.UrlReplacement("/pre/", 1, HttpStatus.SC_ACCEPTED) ); List disabledProducers = Arrays.asList(2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17); @@ -966,8 +966,8 @@ public void recoveryUnsuccessfulRetryTest() throws InitException { rabbitProducer.send(snapshotComplete); waitAndCheckTillTimeout( - w -> checkProducerFlaggedDown(producerId, false), - "Producer is not down", + w -> checkProducerFlaggedDown(producerId, true), + "Producer is down", 1000, 10000 ); @@ -1143,7 +1143,7 @@ public void producerNoAlivesMakeRecoveryTest() throws InitException { w -> checkProducerRecovery(producerId, false), "Producer recovery info is not null", 5000, - 62000 + 70000 ); producer = feed.getProducerManager().getProducer(producerId); @@ -2044,7 +2044,7 @@ public void connectionBreakMultiSessionTest() throws InitException { new TestHttpHelper.UrlReplacement("/liveodds/", 1, HttpStatus.SC_ACCEPTED) ); feed.TestHttpHelper.PostResponses.add( - new TestHttpHelper.UrlReplacement("/prematch/", 1, HttpStatus.SC_ACCEPTED) + new TestHttpHelper.UrlReplacement("/pre/", 1, HttpStatus.SC_ACCEPTED) ); feed.TestHttpHelper.PostResponses.add( new TestHttpHelper.UrlReplacement("/vf/", 1, HttpStatus.SC_ACCEPTED) @@ -3140,6 +3140,7 @@ private void waitAndCheckTillTimeout( Helper.durationBetweenDatesInMs(start, new Date()) ) ); + throw new RuntimeException("Condition not met: " + message); } private void disableProducers(List producerIds, OddsFeed feed) { diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/guava/libraryfixtures/Caches.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/guava/libraryfixtures/Caches.java new file mode 100644 index 00000000..ef575655 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/guava/libraryfixtures/Caches.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.testutil.guava.libraryfixtures; + +import com.google.common.cache.Cache; + +public class Caches { + + private Caches() {} + + public static Cache any() { + return new NoOpCache(); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/guava/libraryfixtures/NoOpCache.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/guava/libraryfixtures/NoOpCache.java new file mode 100644 index 00000000..de9c07e6 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/guava/libraryfixtures/NoOpCache.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.testutil.guava.libraryfixtures; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheStats; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + +public class NoOpCache implements Cache { + + @Override + public Object getIfPresent(Object o) { + return null; + } + + @Override + public Object get(Object o, Callable callable) throws ExecutionException { + return null; + } + + @Override + public void put(Object o, Object o2) {} + + @Override + public void putAll(Map map) {} + + @Override + public void invalidate(Object o) {} + + @Override + public long size() { + return 0; + } + + @Override + public CacheStats stats() { + return null; + } + + @Override + public ConcurrentMap asMap() { + return null; + } + + @Override + public void cleanUp() {} + + @Override + public void invalidateAll() {} + + @Override + public void invalidateAll(Iterable iterable) {} + + @Override + public ImmutableMap getAllPresent(Iterable iterable) { + return null; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/javautil/Languages.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/javautil/Languages.java new file mode 100644 index 00000000..08551523 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/javautil/Languages.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.testutil.javautil; + +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Random; + +public class Languages { + + private static Random random = new Random(); + + private Languages() {} + + public static Locale any() { + List languagePool = Arrays.asList( + Locale.ENGLISH, + Locale.GERMANY, + Locale.CANADA, + Locale.FRENCH, + Locale.CHINESE, + Locale.ITALY, + Locale.JAPAN + ); + return languagePool.get(random.nextInt(languagePool.size())); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/javautil/LanguagesTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/javautil/LanguagesTest.java new file mode 100644 index 00000000..0d90a21a --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/javautil/LanguagesTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.testutil.javautil; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.Test; + +public class LanguagesTest { + + private final int sampleSize = 100; + private List languages = Stream + .generate(() -> Languages.any()) + .limit(sampleSize) + .distinct() + .collect(Collectors.toList()); + + @Test + public void anyLanguageGeneratesNotAlwaysTheSameLanguages() { + assertThat(languages).hasSizeGreaterThan(1); + } + + @Test + public void anyLanguageDoesNotGenerateNullLanguages() { + assertThat(languages).doesNotContainNull(); + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/serialization/JavaSerializer.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/serialization/JavaSerializer.java new file mode 100644 index 00000000..f45afd11 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/serialization/JavaSerializer.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.testutil.serialization; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +public class JavaSerializer { + + private JavaSerializer() {} + + public static byte[] serialize(Object ob) throws Exception { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); + objectOutputStream.writeObject(ob); + byte[] result = byteArrayOutputStream.toByteArray(); + objectOutputStream.close(); + byteArrayOutputStream.close(); + return result; + } + + public static T deserialize(byte[] bytes) throws Exception { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); + ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); + T object = (T) objectInputStream.readObject(); + objectInputStream.close(); + byteArrayInputStream.close(); + return object; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/serialization/JavaSerializerTest.java b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/serialization/JavaSerializerTest.java new file mode 100644 index 00000000..72d78499 --- /dev/null +++ b/sdk-core/src/test/java/com/sportradar/unifiedodds/sdk/testutil/serialization/JavaSerializerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) Sportradar AG. See LICENSE for full license governing this code + */ +package com.sportradar.unifiedodds.sdk.testutil.serialization; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.NotSerializableException; +import java.io.Serializable; +import java.io.StreamCorruptedException; +import lombok.Getter; +import lombok.Setter; +import lombok.val; +import org.junit.Test; + +public class JavaSerializerTest { + + private static final byte[] RANDOM_BYTES = { 4, 7, 5, 1, 2, 4, 5, 6 }; + + @Test + public void notSerializesObjectWhichAreNotMarkedAsSerializable() throws Exception { + assertThatThrownBy(() -> JavaSerializer.serialize(new NotMarkedWithSerializable())) + .isInstanceOf(NotSerializableException.class); + } + + @Test + public void doesNotDeserializeBytesWhichDoesNotRepresentObject() throws Exception { + assertThatThrownBy(() -> JavaSerializer.deserialize(RANDOM_BYTES)) + .isInstanceOf(StreamCorruptedException.class); + } + + @Test + public void serializationProcessShouldPreserveFieldValuesSetInitially() throws Exception { + String value = "someValue"; + val originalObject = new SerializableClass(); + originalObject.setSomeField(value); + + val deserialized = (SerializableClass) JavaSerializer.deserialize( + JavaSerializer.serialize(originalObject) + ); + + assertThat(deserialized.getSomeField()).isEqualTo(value); + } + + @Test + public void deserializationCreatesAnotherInstanceOfTheObject() throws Exception { + String value = "someValue"; + val originalObject = new SerializableClass(); + originalObject.setSomeField(value); + + val deserialized = (SerializableClass) JavaSerializer.deserialize( + JavaSerializer.serialize(originalObject) + ); + + assertThat(deserialized).isNotSameAs(originalObject); + } + + public static class NotMarkedWithSerializable { + + private int anyField; + } + + @Setter + @Getter + public static class SerializableClass implements Serializable { + + private String someField; + } +} diff --git a/sdk-core/src/test/java/com/sportradar/utils/Urns.java b/sdk-core/src/test/java/com/sportradar/utils/Urns.java index 79a849ed..ef6641c7 100644 --- a/sdk-core/src/test/java/com/sportradar/utils/Urns.java +++ b/sdk-core/src/test/java/com/sportradar/utils/Urns.java @@ -31,6 +31,11 @@ public static URN urnForAnySeason() { final int anyId = 277; return URN.parse("sr:season:" + anyId); } + + public static URN urnForAnyStage() { + String anyId = "338"; + return URN.parse("sr:stage:" + anyId); + } } public static class Sports { diff --git a/sdk-core/src/test/java/com/sportradar/utils/UrnsTest.java b/sdk-core/src/test/java/com/sportradar/utils/UrnsTest.java index 00c365d4..e3f9898f 100644 --- a/sdk-core/src/test/java/com/sportradar/utils/UrnsTest.java +++ b/sdk-core/src/test/java/com/sportradar/utils/UrnsTest.java @@ -7,74 +7,94 @@ import lombok.val; import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +@RunWith(Enclosed.class) public class UrnsTest { public static final String SR = "sr"; - @Test - public void shouldGetFootballUrn() { - val footballId = 1L; - URN forFootball = Urns.Sports.getForFootball(); + private UrnsTest() {} - assertEquals(SR, forFootball.getPrefix()); - assertEquals("sport", forFootball.getType()); - assertEquals(footballId, forFootball.getId()); - } + public static class CreatesSportUrns { - @Test - public void shouldGetUrnForAnySport() { - URN forSport = Urns.Sports.urnForAnySport(); + @Test + public void forFootball() { + val footballId = 1L; + URN forFootball = Urns.Sports.getForFootball(); - assertEquals(SR, forSport.getPrefix()); - assertEquals("sport", forSport.getType()); - assertTrue(forSport.getId() > 1); - } + assertEquals(SR, forFootball.getPrefix()); + assertEquals("sport", forFootball.getType()); + assertEquals(footballId, forFootball.getId()); + } - @Test - public void shouldGetUrnForMatch() { - URN match = Urns.SportEvents.getForAnyMatch(); + @Test + public void forAny() { + URN forSport = Urns.Sports.urnForAnySport(); - assertEquals(SR, match.getPrefix()); - assertEquals("match", match.getType()); - assertTrue(match.getId() > 0L); + assertEquals(SR, forSport.getPrefix()); + assertEquals("sport", forSport.getType()); + assertTrue(forSport.getId() > 1); + } } - @Test - public void shouldProvideUrnForTournament() { - URN tournament = Urns.SportEvents.urnForAnyTournament(); + public static class CreateSportEventUrns { - assertEquals(SR, tournament.getPrefix()); - assertEquals("tournament", tournament.getType()); - assertTrue(tournament.getId() > 0L); - } + @Test + public void forAnyMatch() { + URN match = Urns.SportEvents.getForAnyMatch(); - @Test - public void shouldProvideUrnForTournamentWithSpecificId() { - final int tournamentId = 334; + assertEquals(SR, match.getPrefix()); + assertEquals("match", match.getType()); + assertTrue(match.getId() > 0L); + } - URN tournament = Urns.SportEvents.urnForTournamentWithId(tournamentId); + @Test + public void forAnyTournament() { + URN tournament = Urns.SportEvents.urnForAnyTournament(); - assertEquals(SR, tournament.getPrefix()); - assertEquals("tournament", tournament.getType()); - assertEquals(tournamentId, tournament.getId()); - } + assertEquals(SR, tournament.getPrefix()); + assertEquals("tournament", tournament.getType()); + assertTrue(tournament.getId() > 0L); + } - @Test - public void shouldProvideUrnForSimpleTournament() { - URN tournament = Urns.SportEvents.urnForAnySimpleTournament(); + @Test + public void forTournamentWithSpecificId() { + final int tournamentId = 334; - assertEquals(SR, tournament.getPrefix()); - assertEquals("simple_tournament", tournament.getType()); - assertTrue(tournament.getId() > 0L); - } + URN tournament = Urns.SportEvents.urnForTournamentWithId(tournamentId); + + assertEquals(SR, tournament.getPrefix()); + assertEquals("tournament", tournament.getType()); + assertEquals(tournamentId, tournament.getId()); + } + + @Test + public void forAnySimpleTournament() { + URN tournament = Urns.SportEvents.urnForAnySimpleTournament(); + + assertEquals(SR, tournament.getPrefix()); + assertEquals("simple_tournament", tournament.getType()); + assertTrue(tournament.getId() > 0L); + } + + @Test + public void forSeason() { + URN season = Urns.SportEvents.urnForAnySeason(); + + assertEquals(SR, season.getPrefix()); + assertEquals("season", season.getType()); + assertTrue(season.getId() > 0L); + } - @Test - public void shouldProvideUrnForSeason() { - URN season = Urns.SportEvents.urnForAnySeason(); + @Test + public void forAnyStage() { + URN stage = Urns.SportEvents.urnForAnyStage(); - assertEquals(SR, season.getPrefix()); - assertEquals("season", season.getType()); - assertTrue(season.getId() > 0L); + assertEquals(SR, stage.getPrefix()); + assertEquals("stage", stage.getType()); + assertTrue(stage.getId() > 0L); + } } } diff --git a/sdk-example/pom.xml b/sdk-example/pom.xml index 1c6baa93..27bb91e2 100644 --- a/sdk-example/pom.xml +++ b/sdk-example/pom.xml @@ -8,7 +8,7 @@ unified-feed-sdk-parent com.sportradar.unifiedodds.sdk - 2.0.60 + 2.0.61 com.sportradar.unifiedodds.sdk.example