From 362dd6e44f79797a9543858fb82c6eef224988d2 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 12 Apr 2026 12:48:48 +0200 Subject: [PATCH 1/5] refactor: reorganize test config into proper package structure --- .../tp2intervals/ApplicationContextIT.kt | 2 +- .../app/workout/TrainerRoadWorkoutServiceIT.kt | 2 +- .../app/workout/TrainingPeaksWorkoutServiceIT.kt | 2 +- .../app/workout/WorkoutJobSchedulerIT.kt | 2 +- .../tp2intervals}/config/BaseSpringITConfig.kt | 5 ++--- .../freekode/tp2intervals}/config/TestUtils.kt | 16 ++++++++-------- .../config/mock/IntervalsApiClientMock.kt | 2 +- .../config/mock/ObjectMapperFactory.kt | 2 +- .../config/mock/TrainerRoadApiClientMock.kt | 2 +- .../config/mock/TrainingPeaksApiClientMock.kt | 3 +-- .../workout/IntervalsWorkoutRepositoryTest.kt | 6 +++--- .../workout/TrainerRoadWorkoutRepositoryTest.kt | 4 ++-- .../TrainingPeaksWorkoutRepositoryTest.kt | 6 +++--- 13 files changed, 26 insertions(+), 28 deletions(-) rename boot/src/test/kotlin/{ => org/freekode/tp2intervals}/config/BaseSpringITConfig.kt (96%) rename boot/src/test/kotlin/{ => org/freekode/tp2intervals}/config/TestUtils.kt (54%) rename boot/src/test/kotlin/{ => org/freekode/tp2intervals}/config/mock/IntervalsApiClientMock.kt (97%) rename boot/src/test/kotlin/{ => org/freekode/tp2intervals}/config/mock/ObjectMapperFactory.kt (95%) rename boot/src/test/kotlin/{ => org/freekode/tp2intervals}/config/mock/TrainerRoadApiClientMock.kt (97%) rename boot/src/test/kotlin/{ => org/freekode/tp2intervals}/config/mock/TrainingPeaksApiClientMock.kt (94%) diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/ApplicationContextIT.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/ApplicationContextIT.kt index f2f67817..bb6d13ff 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/ApplicationContextIT.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/ApplicationContextIT.kt @@ -1,6 +1,6 @@ package org.freekode.tp2intervals -import config.BaseSpringITConfig +import org.freekode.tp2intervals.config.BaseSpringITConfig import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/TrainerRoadWorkoutServiceIT.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/TrainerRoadWorkoutServiceIT.kt index 2a10c6a0..f5f6f50c 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/TrainerRoadWorkoutServiceIT.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/TrainerRoadWorkoutServiceIT.kt @@ -1,6 +1,6 @@ package org.freekode.tp2intervals.app.workout -import config.BaseSpringITConfig +import org.freekode.tp2intervals.config.BaseSpringITConfig import org.freekode.tp2intervals.app.plan.CreateLibraryContainerRequest import org.freekode.tp2intervals.app.plan.DeleteLibraryRequest import org.freekode.tp2intervals.app.plan.LibraryService diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/TrainingPeaksWorkoutServiceIT.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/TrainingPeaksWorkoutServiceIT.kt index 4f10d188..dbcb0cef 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/TrainingPeaksWorkoutServiceIT.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/TrainingPeaksWorkoutServiceIT.kt @@ -1,6 +1,6 @@ package org.freekode.tp2intervals.app.workout -import config.BaseSpringITConfig +import org.freekode.tp2intervals.config.BaseSpringITConfig import org.freekode.tp2intervals.app.plan.CopyLibraryRequest import org.freekode.tp2intervals.app.plan.DeleteLibraryRequest import org.freekode.tp2intervals.app.plan.LibraryService diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutJobSchedulerIT.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutJobSchedulerIT.kt index 0c77af31..74cb66dc 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutJobSchedulerIT.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutJobSchedulerIT.kt @@ -1,6 +1,6 @@ package org.freekode.tp2intervals.app.workout -import config.BaseSpringITConfig +import org.freekode.tp2intervals.config.BaseSpringITConfig import org.assertj.core.api.Assertions.assertThat import org.freekode.tp2intervals.app.workout.schedule.C2CTodayScheduledRequest import org.freekode.tp2intervals.app.workout.schedule.WorkoutScheduledJob diff --git a/boot/src/test/kotlin/config/BaseSpringITConfig.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/config/BaseSpringITConfig.kt similarity index 96% rename from boot/src/test/kotlin/config/BaseSpringITConfig.kt rename to boot/src/test/kotlin/org/freekode/tp2intervals/config/BaseSpringITConfig.kt index 691be318..9eef70cd 100644 --- a/boot/src/test/kotlin/config/BaseSpringITConfig.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/config/BaseSpringITConfig.kt @@ -1,4 +1,4 @@ -package config +package org.freekode.tp2intervals.config import com.github.tomakehurst.wiremock.core.WireMockConfiguration import com.github.tomakehurst.wiremock.junit5.WireMockExtension @@ -9,7 +9,6 @@ import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource - @SpringBootTest @ActiveProfiles("it", "dev") abstract class BaseSpringITConfig { @@ -33,4 +32,4 @@ abstract class BaseSpringITConfig { Thread.sleep(1000) // wait for default properties to save } } -} +} \ No newline at end of file diff --git a/boot/src/test/kotlin/config/TestUtils.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/config/TestUtils.kt similarity index 54% rename from boot/src/test/kotlin/config/TestUtils.kt rename to boot/src/test/kotlin/org/freekode/tp2intervals/config/TestUtils.kt index d2cca2c2..351b7713 100644 --- a/boot/src/test/kotlin/config/TestUtils.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/config/TestUtils.kt @@ -1,18 +1,18 @@ -package config +package org.freekode.tp2intervals.config -import org.freekode.tp2intervals.domain.workout.structure.StepLength import org.freekode.tp2intervals.domain.workout.structure.SingleStep +import org.freekode.tp2intervals.domain.workout.structure.StepLength import org.freekode.tp2intervals.domain.workout.structure.WorkoutStep -import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions class TestUtils { companion object { fun assertStep(step: WorkoutStep, length: Long, lengthUnit: StepLength.LengthUnit, targetStart: Int, targetEnd: Int) { val singleStep = step as SingleStep - assertEquals(length, singleStep.length.value) - assertEquals(lengthUnit, singleStep.length.unit) - assertEquals(targetStart, singleStep.target.start) - assertEquals(targetEnd, singleStep.target.end) + Assertions.assertEquals(length, singleStep.length.value) + Assertions.assertEquals(lengthUnit, singleStep.length.unit) + Assertions.assertEquals(targetStart, singleStep.target.start) + Assertions.assertEquals(targetEnd, singleStep.target.end) } } -} +} \ No newline at end of file diff --git a/boot/src/test/kotlin/config/mock/IntervalsApiClientMock.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/IntervalsApiClientMock.kt similarity index 97% rename from boot/src/test/kotlin/config/mock/IntervalsApiClientMock.kt rename to boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/IntervalsApiClientMock.kt index bad9fe37..a98575e7 100644 --- a/boot/src/test/kotlin/config/mock/IntervalsApiClientMock.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/IntervalsApiClientMock.kt @@ -1,4 +1,4 @@ -package config.mock +package org.freekode.tp2intervals.config.mock import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper diff --git a/boot/src/test/kotlin/config/mock/ObjectMapperFactory.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/ObjectMapperFactory.kt similarity index 95% rename from boot/src/test/kotlin/config/mock/ObjectMapperFactory.kt rename to boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/ObjectMapperFactory.kt index e8d07518..2dcfaae1 100644 --- a/boot/src/test/kotlin/config/mock/ObjectMapperFactory.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/ObjectMapperFactory.kt @@ -1,4 +1,4 @@ -package config.mock +package org.freekode.tp2intervals.config.mock import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.DeserializationFeature diff --git a/boot/src/test/kotlin/config/mock/TrainerRoadApiClientMock.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/TrainerRoadApiClientMock.kt similarity index 97% rename from boot/src/test/kotlin/config/mock/TrainerRoadApiClientMock.kt rename to boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/TrainerRoadApiClientMock.kt index 136df974..2d545f9e 100644 --- a/boot/src/test/kotlin/config/mock/TrainerRoadApiClientMock.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/TrainerRoadApiClientMock.kt @@ -1,4 +1,4 @@ -package config.mock +package org.freekode.tp2intervals.config.mock import com.fasterxml.jackson.databind.ObjectMapper import java.io.InputStream diff --git a/boot/src/test/kotlin/config/mock/TrainingPeaksApiClientMock.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/TrainingPeaksApiClientMock.kt similarity index 94% rename from boot/src/test/kotlin/config/mock/TrainingPeaksApiClientMock.kt rename to boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/TrainingPeaksApiClientMock.kt index 7deba103..0c926f7d 100644 --- a/boot/src/test/kotlin/config/mock/TrainingPeaksApiClientMock.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/config/mock/TrainingPeaksApiClientMock.kt @@ -1,8 +1,7 @@ -package config.mock +package org.freekode.tp2intervals.config.mock import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper -import org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout.IntervalsEventDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.TrainingPeaksApiClient import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.CreateTPWorkoutRequestDTO import org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.TPNoteResponseDTO diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryTest.kt index 22e46781..dba84638 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsWorkoutRepositoryTest.kt @@ -1,8 +1,8 @@ package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout -import config.TestUtils -import config.mock.IntervalsApiClientMock -import config.mock.ObjectMapperFactory +import org.freekode.tp2intervals.config.TestUtils +import org.freekode.tp2intervals.config.mock.IntervalsApiClientMock +import org.freekode.tp2intervals.config.mock.ObjectMapperFactory import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.workout.Workout import org.freekode.tp2intervals.domain.workout.structure.SingleStep diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.kt index 6b212767..34d291d4 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainerroad/workout/TrainerRoadWorkoutRepositoryTest.kt @@ -1,7 +1,7 @@ package org.freekode.tp2intervals.infrastructure.platform.trainerroad.workout -import config.mock.ObjectMapperFactory -import config.mock.TrainerRoadApiClientMock +import org.freekode.tp2intervals.config.mock.ObjectMapperFactory +import org.freekode.tp2intervals.config.mock.TrainerRoadApiClientMock import org.freekode.tp2intervals.domain.ExternalData import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.workout.structure.SingleStep diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.kt index 4798e882..79e25320 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/TrainingPeaksWorkoutRepositoryTest.kt @@ -1,8 +1,8 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout -import config.TestUtils -import config.mock.ObjectMapperFactory -import config.mock.TrainingPeaksApiClientMock +import org.freekode.tp2intervals.config.TestUtils +import org.freekode.tp2intervals.config.mock.ObjectMapperFactory +import org.freekode.tp2intervals.config.mock.TrainingPeaksApiClientMock import org.freekode.tp2intervals.domain.TrainingType import org.freekode.tp2intervals.domain.workout.structure.StepLength import org.freekode.tp2intervals.domain.workout.structure.MultiStep From f3d50e9e264ec241a9b63dc36adecba158d20d91 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 12 Apr 2026 14:22:10 +0200 Subject: [PATCH 2/5] docs: update AGENTS.md with current project structure --- AGENTS.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e7b680df..a9bf797a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,10 +2,12 @@ ## Project Structure -- `boot/` - Kotlin/Spring Boot backend (JDK 21) -- `ui/` - Angular 17 frontend -- `electron/` - Electron desktop app wrapper +- `boot/` - Kotlin/Spring Boot backend (JDK 21) with SQLite, Liquibase, Spring Cloud OpenFeign +- `ui/` - Angular 17 frontend with Angular Material, SCSS +- `electron/` - Electron desktop app using electron-vite + electron-builder - `cypress/` - End-to-end tests +- `jdktool/` - Utility for downloading JDK for Electron packaging +- `docker-compose.yml` - Docker deployment config ## Build Commands @@ -23,8 +25,8 @@ cd ui && npm install && npm run build ### Electron App ```bash -cd ui && npm run build --prefix ui -- --base-href=./ # Build UI first -cd electron && npm start # Dev mode +cd electron && npm run build # Builds UI, downloads JDK, packages app +cd electron && npm run start # Dev mode (runs UI + Electron) ``` ### End-to-End Tests @@ -36,8 +38,10 @@ npm test --prefix cypress ## Key Facts - JAR output: `boot/build/libs/tp2intervals.jar` -- UI is built to `ui/dist/ui/browser` and copied to `boot/src/main/resources/static` during CI +- UI is built to `ui/dist/ui/browser` and served by Spring Boot - Version: read from `boot/version` - Backend tests use WireMock (files in `config/mock/`) -- Integration tests named `*IT.kt` +- **Unit tests named `*Test.kt`** +- **Integration tests named `*IT.kt`** - Frontend uses SCSS, Angular Material, proxy config at `ui/src/proxy.conf.json` +- Electron app downloads JDK dynamically during build (platform-specific) From 40928702717bdb080a741b2df3e99a14b3d97aad Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 12 Apr 2026 14:22:50 +0200 Subject: [PATCH 3/5] test: add unit tests for activity, plan, workout services and utils --- boot/build.gradle.kts | 6 + .../app/activity/ActivityServiceTest.kt | 98 ++++++++ .../app/plan/LibraryServiceTest.kt | 124 ++++++++++ .../app/workout/WorkoutServiceTest.kt | 183 ++++++++++++++ .../workout/structure/SingleStepTest.kt | 53 +++++ .../workout/structure/StepTargetTest.kt | 28 +++ .../workout/structure/WorkoutStructureTest.kt | 64 +++++ .../workout/IntervalsToTargetConverterTest.kt | 134 +++++++++++ .../structure/FromTPStructureConverterTest.kt | 211 +++++++++++++++++ .../infrastructure/utils/AuthTest.kt | 35 +++ .../infrastructure/utils/Base64Test.kt | 47 ++++ .../infrastructure/utils/DateTest.kt | 80 +++++++ .../infrastructure/utils/MathTest.kt | 37 +++ .../utils/RampConverterMockkTest.kt | 44 ++++ .../tp2intervals/utils/RampConverterTest.kt | 223 ++++++++++++++++++ 15 files changed, 1367 insertions(+) create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/app/activity/ActivityServiceTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/app/plan/LibraryServiceTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutServiceTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/SingleStepTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/StepTargetTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructureTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToTargetConverterTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/FromTPStructureConverterTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/AuthTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64Test.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/DateTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/MathTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterMockkTest.kt create mode 100644 boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterTest.kt diff --git a/boot/build.gradle.kts b/boot/build.gradle.kts index 0d5ad894..505ec584 100644 --- a/boot/build.gradle.kts +++ b/boot/build.gradle.kts @@ -47,6 +47,12 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.junit.jupiter:junit-jupiter-engine") testImplementation("org.wiremock:wiremock-standalone:3.5.2") + testImplementation("io.mockk:mockk:1.13.10") + testImplementation("io.strikt:strikt-core:0.34.1") +} + +tasks.withType { + systemProperty("kotlin.daemon.jvm.options", "-Xmx1024m") } springBoot { diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/app/activity/ActivityServiceTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/app/activity/ActivityServiceTest.kt new file mode 100644 index 00000000..d1f45b9a --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/app/activity/ActivityServiceTest.kt @@ -0,0 +1,98 @@ +package org.freekode.tp2intervals.app.activity + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.TrainingType +import org.freekode.tp2intervals.domain.activity.Activity +import org.freekode.tp2intervals.domain.activity.ActivityRepository +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.time.LocalDate +import java.time.LocalDateTime + +class ActivityServiceTest { + + private val trainingPeaksRepo: ActivityRepository = mockk() + private val intervalsRepo: ActivityRepository = mockk() + + private lateinit var service: ActivityService + + @BeforeEach + fun setup() { + every { trainingPeaksRepo.platform() } returns Platform.TRAINING_PEAKS + every { intervalsRepo.platform() } returns Platform.INTERVALS + + service = ActivityService(listOf(trainingPeaksRepo, intervalsRepo)) + } + + @Test + fun `should sync activities with resource`() { + val startDate = LocalDate.of(2024, 1, 1) + val endDate = LocalDate.of(2024, 1, 7) + val activity1 = createActivity(TrainingType.RIDE, "Ride 1", "resource-data-1") + val activity2 = createActivity(TrainingType.RUN, "Run 1", "resource-data-2") + every { trainingPeaksRepo.getActivities(startDate, endDate, TrainingType.DEFAULT_LIST) } returns listOf(activity1, activity2) + + val request = CopyActivitiesRequest( + startDate, endDate, + TrainingType.DEFAULT_LIST, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + val response = service.syncActivities(request) + + assert(response.copied == 2) + assert(response.filteredOut == 0) + verify { intervalsRepo.saveActivities(listOf(activity1, activity2)) } + } + + @Test + fun `should filter activities without resource`() { + val startDate = LocalDate.of(2024, 1, 1) + val endDate = LocalDate.of(2024, 1, 7) + val activityWithResource = createActivity(TrainingType.RIDE, "Ride", "resource") + val activityWithoutResource = createActivity(TrainingType.RUN, "Run", null) + every { trainingPeaksRepo.getActivities(startDate, endDate, TrainingType.DEFAULT_LIST) } returns listOf(activityWithResource, activityWithoutResource) + + val request = CopyActivitiesRequest( + startDate, endDate, + TrainingType.DEFAULT_LIST, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + val response = service.syncActivities(request) + + assert(response.copied == 1) + assert(response.filteredOut == 1) + verify { intervalsRepo.saveActivities(match { it.first().title == "Ride" }) } + } + + @Test + fun `should return empty response when no activities`() { + val startDate = LocalDate.of(2024, 1, 1) + val endDate = LocalDate.of(2024, 1, 7) + every { trainingPeaksRepo.getActivities(startDate, endDate, TrainingType.DEFAULT_LIST) } returns emptyList() + + val request = CopyActivitiesRequest( + startDate, endDate, + TrainingType.DEFAULT_LIST, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + val response = service.syncActivities(request) + + assert(response.copied == 0) + assert(response.filteredOut == 0) + } + + private fun createActivity(type: TrainingType, title: String, resource: String?): Activity { + return Activity( + startedAt = LocalDateTime.now(), + type = type, + title = title, + resource = resource + ) + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/app/plan/LibraryServiceTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/app/plan/LibraryServiceTest.kt new file mode 100644 index 00000000..5f29b318 --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/app/plan/LibraryServiceTest.kt @@ -0,0 +1,124 @@ +package org.freekode.tp2intervals.app.plan + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.freekode.tp2intervals.domain.ExternalData +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.TrainingType +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainerRepository +import org.freekode.tp2intervals.domain.workout.Workout +import org.freekode.tp2intervals.domain.workout.WorkoutDetails +import org.freekode.tp2intervals.domain.workout.WorkoutRepository +import org.freekode.tp2intervals.domain.workout.structure.StepModifier +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class LibraryServiceTest { + + private val trainingPeaksRepo: WorkoutRepository = mockk() + private val intervalsRepo: WorkoutRepository = mockk() + private val trainingPeaksPlanRepo: LibraryContainerRepository = mockk() + private val intervalsPlanRepo: LibraryContainerRepository = mockk() + + private lateinit var service: LibraryService + + @BeforeEach + fun setup() { + every { trainingPeaksRepo.platform() } returns Platform.TRAINING_PEAKS + every { intervalsRepo.platform() } returns Platform.INTERVALS + every { trainingPeaksPlanRepo.platform() } returns Platform.TRAINING_PEAKS + every { intervalsPlanRepo.platform() } returns Platform.INTERVALS + + service = LibraryService( + listOf(trainingPeaksRepo, intervalsRepo), + listOf(trainingPeaksPlanRepo, intervalsPlanRepo) + ) + } + + @Test + fun `should find libraries by platform`() { + val library = LibraryContainer("My Plan", LocalDate.now(), true, 10, ExternalData.empty()) + every { trainingPeaksPlanRepo.getLibraryContainers() } returns listOf(library) + + val result = service.findByPlatform(Platform.TRAINING_PEAKS) + + assert(result.size == 1) + assert(result.first().name == "My Plan") + } + + @Test + fun `should copy library with workouts`() { + val sourceLibrary = LibraryContainer("Source Plan", LocalDate.now(), true, 5, ExternalData.empty()) + val targetLibrary = LibraryContainer("Target Plan", LocalDate.now(), true, 3, ExternalData.empty()) + val workout1 = createWorkout("Workout 1", LocalDate.now()) + val workout2 = createWorkout("Workout 2", LocalDate.now().plusDays(1)) + every { trainingPeaksRepo.getWorkoutsFromLibrary(sourceLibrary) } returns listOf(workout1, workout2) + every { intervalsPlanRepo.createLibraryContainer("New Plan", true, LocalDate.now()) } returns targetLibrary + + val request = CopyLibraryRequest( + libraryContainer = sourceLibrary, + newName = "New Plan", + stepModifier = StepModifier.NONE, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + val response = service.copyLibrary(request) + + assert(response.planName == "Target Plan") + assert(response.workouts == 2) + verify { intervalsRepo.saveWorkoutsToLibrary(targetLibrary, match { it.size == 2 }) } + } + + @Test + fun `should delete library`() { + val externalData = ExternalData.empty() + + service.deleteLibrary(DeleteLibraryRequest(externalData, Platform.INTERVALS)) + + verify { intervalsPlanRepo.deleteLibraryContainer(externalData) } + } + + @Test + fun `should create library container`() { + val library = LibraryContainer("New Library", LocalDate.now(), false, 0, ExternalData.empty()) + every { intervalsPlanRepo.createLibraryContainer("New Library", false, null) } returns library + + val result = service.create(CreateLibraryContainerRequest("New Library", Platform.INTERVALS)) + + assert(result.name == "New Library") + assert(result.isPlan == false) + } + + @Test + fun `should apply step modifier when copying library`() { + val sourceLibrary = LibraryContainer("Source", LocalDate.now(), true, 1, ExternalData.empty()) + val targetLibrary = LibraryContainer("Target", LocalDate.now(), true, 1, ExternalData.empty()) + val workout = createWorkout("Workout", LocalDate.now()) + every { trainingPeaksRepo.getWorkoutsFromLibrary(sourceLibrary) } returns listOf(workout) + every { intervalsPlanRepo.createLibraryContainer(any(), any(), any()) } returns targetLibrary + + val request = CopyLibraryRequest( + libraryContainer = sourceLibrary, + newName = "New", + stepModifier = StepModifier.WARMUP, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + service.copyLibrary(request) + + verify { intervalsRepo.saveWorkoutsToLibrary(targetLibrary, match { it.first().structure?.modifier == StepModifier.WARMUP }) } + } + + private fun createWorkout(name: String, date: LocalDate): Workout { + return Workout( + WorkoutDetails( + TrainingType.RIDE, name, null, null, null, ExternalData.empty() + ), + date, + null + ) + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutServiceTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutServiceTest.kt new file mode 100644 index 00000000..f81e3be0 --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutServiceTest.kt @@ -0,0 +1,183 @@ +package org.freekode.tp2intervals.app.workout + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.freekode.tp2intervals.domain.ExternalData +import org.freekode.tp2intervals.domain.Platform +import org.freekode.tp2intervals.domain.TrainingType +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainer +import org.freekode.tp2intervals.domain.librarycontainer.LibraryContainerRepository +import org.freekode.tp2intervals.domain.workout.Workout +import org.freekode.tp2intervals.domain.workout.WorkoutDetails +import org.freekode.tp2intervals.domain.workout.WorkoutRepository +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.time.LocalDate + +class WorkoutServiceTest { + + private val trainingPeaksRepo: WorkoutRepository = mockk() + private val intervalsRepo: WorkoutRepository = mockk() + private val trainingPeaksPlanRepo: LibraryContainerRepository = mockk() + private val intervalsPlanRepo: LibraryContainerRepository = mockk() + + private lateinit var service: WorkoutService + + @BeforeEach + fun setup() { + every { trainingPeaksRepo.platform() } returns Platform.TRAINING_PEAKS + every { intervalsRepo.platform() } returns Platform.INTERVALS + every { trainingPeaksPlanRepo.platform() } returns Platform.TRAINING_PEAKS + every { intervalsPlanRepo.platform() } returns Platform.INTERVALS + + service = WorkoutService( + listOf(trainingPeaksRepo, intervalsRepo), + listOf(trainingPeaksPlanRepo, intervalsPlanRepo) + ) + } + + @Test + fun `should copy workouts from calendar to calendar`() { + val startDate = LocalDate.of(2024, 1, 1) + val endDate = LocalDate.of(2024, 1, 7) + val workout1 = createWorkout(TrainingType.RIDE, "Workout 1") + val workout2 = createWorkout(TrainingType.RUN, "Workout 2") + every { trainingPeaksRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(workout1, workout2) + every { intervalsRepo.getWorkoutsFromCalendar(startDate, endDate) } returns emptyList() + + val request = CopyFromCalendarToCalendarRequest( + startDate, endDate, + listOf(TrainingType.RIDE), + skipSynced = false, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + val response = service.copyWorkoutsC2C(request) + + assert(response.copied == 1) + assert(response.filteredOut == 0) + verify { intervalsRepo.saveWorkoutsToCalendar(listOf(workout1)) } + } + + @Test + fun `should skip already synced workouts when flag is set`() { + val startDate = LocalDate.of(2024, 1, 1) + val endDate = LocalDate.of(2024, 1, 7) + val workout1 = createWorkout(TrainingType.RIDE, "Workout 1") + every { trainingPeaksRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(workout1) + every { intervalsRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(workout1) + + val request = CopyFromCalendarToCalendarRequest( + startDate, endDate, + TrainingType.DEFAULT_LIST, + skipSynced = true, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + val response = service.copyWorkoutsC2C(request) + + assert(response.copied == 0) + assert(response.filteredOut == 1) + } + + @Test + fun `should filter workouts by training type`() { + val startDate = LocalDate.of(2024, 1, 1) + val endDate = LocalDate.of(2024, 1, 7) + val rideWorkout = createWorkout(TrainingType.RIDE, "Ride") + val runWorkout = createWorkout(TrainingType.RUN, "Run") + every { trainingPeaksRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(rideWorkout, runWorkout) + every { intervalsRepo.getWorkoutsFromCalendar(startDate, endDate) } returns emptyList() + + val request = CopyFromCalendarToCalendarRequest( + startDate, endDate, + listOf(TrainingType.RUN), + skipSynced = false, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + val response = service.copyWorkoutsC2C(request) + + assert(response.copied == 1) + verify { intervalsRepo.saveWorkoutsToCalendar(match { it.first().details.name == "Run" }) } + } + + @Test + fun `should copy workouts from calendar to library`() { + val startDate = LocalDate.of(2024, 1, 1) + val endDate = LocalDate.of(2024, 1, 7) + val workout = createWorkout(TrainingType.RIDE, "Workout") + val newLibrary = LibraryContainer("New Library", startDate, true, 1, ExternalData.empty()) + every { trainingPeaksRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(workout) + every { intervalsPlanRepo.createLibraryContainer("My Library", true, startDate) } returns newLibrary + + val request = CopyFromCalendarToLibraryRequest( + startDate, endDate, + "My Library", + isPlan = true, + types = TrainingType.DEFAULT_LIST, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + val response = service.copyWorkoutsC2L(request) + + assert(response.copied == 1) + verify { intervalsRepo.saveWorkoutsToLibrary(newLibrary, listOf(workout)) } + } + + @Test + fun `should copy workouts from library to library`() { + val workout = createWorkout(TrainingType.RIDE, "Workout") + val targetLibrary = LibraryContainer("Target", LocalDate.now(), true, 1, ExternalData.empty()) + every { trainingPeaksRepo.getWorkoutFromLibrary(ExternalData.empty()) } returns workout + + val request = CopyFromLibraryToLibraryRequest( + workoutExternalData = ExternalData.empty(), + targetLibraryContainer = targetLibrary, + sourcePlatform = Platform.TRAINING_PEAKS, + targetPlatform = Platform.INTERVALS + ) + val response = service.copyWorkoutL2L(request) + + assert(response.copied == 1) + verify { intervalsRepo.saveWorkoutsToLibrary(targetLibrary, listOf(workout)) } + } + + @Test + fun `should find workouts by name`() { + val expectedDetails = WorkoutDetails( + TrainingType.RIDE, "Evening Ride", null, null, null, ExternalData.empty() + ) + every { trainingPeaksRepo.findWorkoutsFromLibraryByName("Evening") } returns listOf(expectedDetails) + + val result = service.findWorkoutsByName(Platform.TRAINING_PEAKS, "Evening") + + assert(result.size == 1) + assert(result.first().name == "Evening Ride") + } + + @Test + fun `should delete workouts from calendar`() { + val startDate = LocalDate.of(2024, 1, 1) + val endDate = LocalDate.of(2024, 1, 7) + + service.deleteWorkoutsFromCalendar( + org.freekode.tp2intervals.rest.workout.DeleteWorkoutRequestDTO( + startDate, endDate, Platform.INTERVALS + ) + ) + + verify { intervalsRepo.deleteWorkoutsFromCalendar(startDate, endDate) } + } + + private fun createWorkout(type: TrainingType, name: String): Workout { + return Workout( + WorkoutDetails( + type, name, null, null, null, ExternalData.empty() + ), + LocalDate.now(), + null + ) + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/SingleStepTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/SingleStepTest.kt new file mode 100644 index 00000000..3d22515f --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/SingleStepTest.kt @@ -0,0 +1,53 @@ +package org.freekode.tp2intervals.domain.workout.structure + +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class SingleStepTest { + + @Test + fun `should identify as single step`() { + val step = SingleStep( + "Step", + StepLength.seconds(60), + StepTarget(75, 75), + null, + false + ) + + assert(step.isSingleStep()) + } + + @Test + fun `should throw when converting non-ramp step`() { + val step = SingleStep( + "Step", + StepLength.seconds(60), + StepTarget(50, 100), + null, + false + ) + + assertThrows { + step.convertRampToMultiStep() + } + } + + @Test + fun `should convert ramp step to multi step`() { + val step = SingleStep( + "Ramp", + StepLength.seconds(180), + StepTarget(50, 100), + null, + true + ) + + val result = step.convertRampToMultiStep() + + assertEquals("Ramp", result.name) + assertEquals(1, result.repetitions) + assertEquals(3, result.steps.size) + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/StepTargetTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/StepTargetTest.kt new file mode 100644 index 00000000..9ae12b7f --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/StepTargetTest.kt @@ -0,0 +1,28 @@ +package org.freekode.tp2intervals.domain.workout.structure + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class StepTargetTest { + + @Test + fun `should return true when start equals end`() { + val target = StepTarget(75, 75) + + assert(target.isSingleValue()) + } + + @Test + fun `should return false when start differs from end`() { + val target = StepTarget(50, 100) + + assert(!target.isSingleValue()) + } + + @Test + fun `should handle negative values`() { + val target = StepTarget(-10, 10) + + assert(!target.isSingleValue()) + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructureTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructureTest.kt new file mode 100644 index 00000000..5e6f33a6 --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructureTest.kt @@ -0,0 +1,64 @@ +package org.freekode.tp2intervals.domain.workout.structure + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class WorkoutStructureTest { + + @Test + fun `should throw when steps is empty`() { + assertThrows { + WorkoutStructure(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, emptyList()) + } + } + + @Test + fun `should create structure with valid steps`() { + val step = SingleStep( + "Step 1", + StepLength.seconds(60), + StepTarget(50, 100), + null, + false + ) + + val structure = WorkoutStructure(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, listOf(step)) + + assertEquals(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, structure.target) + assertEquals(1, structure.steps.size) + } + + @Test + fun `should add modifier to structure`() { + val step = SingleStep( + "Step 1", + StepLength.seconds(60), + StepTarget(50, 100), + null, + false + ) + val structure = WorkoutStructure(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, listOf(step)) + + val modified = structure.addModifier(StepModifier.WARMUP) + + assertEquals(StepModifier.WARMUP, modified.modifier) + assertEquals(structure.steps, modified.steps) + assertEquals(structure.target, modified.target) + } + + @Test + fun `should not modify original when adding modifier`() { + val step = SingleStep( + "Step 1", + StepLength.seconds(60), + StepTarget(50, 100), + null, + false + ) + val structure = WorkoutStructure(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, listOf(step)) + + structure.addModifier(StepModifier.COOLDOWN) + + assertEquals(StepModifier.NONE, structure.modifier) + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToTargetConverterTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToTargetConverterTest.kt new file mode 100644 index 00000000..213fb2c9 --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToTargetConverterTest.kt @@ -0,0 +1,134 @@ +package org.freekode.tp2intervals.infrastructure.platform.intervalsicu.workout + +import org.freekode.tp2intervals.domain.workout.structure.StepTarget +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class IntervalsToTargetConverterTest { + + @Test + fun `should convert power target using FTP`() { + val converter = IntervalsToTargetConverter(ftp = 250.0, lthr = null, paceThreshold = null) + val stepDTO = IntervalsWorkoutDocDTO.WorkoutStepDTO( + text = null, reps = null, duration = null, distance = null, + power = null, _power = IntervalsWorkoutDocDTO.ResolvedStepValueDTO(null, 0.5, 0.75), + hr = null, _hr = null, pace = null, _pace = null, cadence = null, steps = null, + warmup = null, cooldown = null, ramp = null + ) + + val result = converter.toMainTarget(stepDTO) + + assertEquals(StepTarget(125, 188), result) + } + + @Test + fun `should convert HR target using LTHR`() { + val converter = IntervalsToTargetConverter(ftp = null, lthr = 150.0, paceThreshold = null) + val stepDTO = IntervalsWorkoutDocDTO.WorkoutStepDTO( + text = null, reps = null, duration = null, distance = null, + power = null, _power = null, + hr = null, _hr = IntervalsWorkoutDocDTO.ResolvedStepValueDTO(null, 0.7, 0.9), + pace = null, _pace = null, cadence = null, steps = null, + warmup = null, cooldown = null, ramp = null + ) + + val result = converter.toMainTarget(stepDTO) + + assertEquals(StepTarget(105, 135), result) + } + + @Test + fun `should convert pace target using threshold`() { + val converter = IntervalsToTargetConverter(ftp = null, lthr = null, paceThreshold = 300.0) + val stepDTO = IntervalsWorkoutDocDTO.WorkoutStepDTO( + text = null, reps = null, duration = null, distance = null, + power = null, _power = null, hr = null, _hr = null, + pace = null, _pace = IntervalsWorkoutDocDTO.ResolvedStepValueDTO(null, 0.8, 1.0), + cadence = null, steps = null, + warmup = null, cooldown = null, ramp = null + ) + + val result = converter.toMainTarget(stepDTO) + + assertEquals(StepTarget(240, 300), result) + } + + @Test + fun `should throw when no target available`() { + val converter = IntervalsToTargetConverter(ftp = null, lthr = null, paceThreshold = null) + val stepDTO = IntervalsWorkoutDocDTO.WorkoutStepDTO( + text = null, reps = null, duration = null, distance = null, + power = null, _power = null, hr = null, _hr = null, + pace = null, _pace = null, cadence = null, steps = null, + warmup = null, cooldown = null, ramp = null + ) + + assertThrows { + converter.toMainTarget(stepDTO) + } + } + + @Test + fun `should convert cadence with single value`() { + val converter = IntervalsToTargetConverter(ftp = 250.0, lthr = null, paceThreshold = null) + val cadenceDTO = IntervalsWorkoutDocDTO.StepValueDTO( + units = "rpm", + value = 90, + start = null, + end = null + ) + + val result = converter.toCadenceTarget(cadenceDTO) + + assertEquals(StepTarget(90, 90), result) + } + + @Test + fun `should convert cadence with range`() { + val converter = IntervalsToTargetConverter(ftp = 250.0, lthr = null, paceThreshold = null) + val cadenceDTO = IntervalsWorkoutDocDTO.StepValueDTO( + units = "rpm", + value = null, + start = 85, + end = 95 + ) + + val result = converter.toCadenceTarget(cadenceDTO) + + assertEquals(StepTarget(85, 95), result) + } + + @Test + fun `should prioritize power over HR and pace`() { + val converter = IntervalsToTargetConverter(ftp = 200.0, lthr = 150.0, paceThreshold = 300.0) + val stepDTO = IntervalsWorkoutDocDTO.WorkoutStepDTO( + text = null, reps = null, duration = null, distance = null, + power = null, _power = IntervalsWorkoutDocDTO.ResolvedStepValueDTO(null, 0.5, 0.5), + hr = null, _hr = IntervalsWorkoutDocDTO.ResolvedStepValueDTO(null, 0.7, 0.9), + pace = null, _pace = IntervalsWorkoutDocDTO.ResolvedStepValueDTO(null, 0.8, 1.0), + cadence = null, steps = null, + warmup = null, cooldown = null, ramp = null + ) + + val result = converter.toMainTarget(stepDTO) + + assertEquals(StepTarget(100, 100), result) + } + + @Test + fun `should use HR when power not available`() { + val converter = IntervalsToTargetConverter(ftp = null, lthr = 160.0, paceThreshold = null) + val stepDTO = IntervalsWorkoutDocDTO.WorkoutStepDTO( + text = null, reps = null, duration = null, distance = null, + power = null, _power = null, + hr = null, _hr = IntervalsWorkoutDocDTO.ResolvedStepValueDTO(null, 0.8, 0.9), + pace = null, _pace = null, cadence = null, steps = null, + warmup = null, cooldown = null, ramp = null + ) + + val result = converter.toMainTarget(stepDTO) + + assertEquals(StepTarget(128, 144), result) + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/FromTPStructureConverterTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/FromTPStructureConverterTest.kt new file mode 100644 index 00000000..f31e6d6c --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/FromTPStructureConverterTest.kt @@ -0,0 +1,211 @@ +package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout.structure + +import org.freekode.tp2intervals.domain.workout.structure.StepLength +import org.freekode.tp2intervals.domain.workout.structure.StepTarget +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class FromTPStructureConverterTest { + + @Test + fun `should convert single step`() { + val step = TPStepDTO().apply { + name = "Warmup" + length = TPLengthDTO(300, "second") + targets = listOf(TPTargetDTO.mainTarget(50, 75)) + } + val structure = TPWorkoutStructureDTO( + structure = listOf(TPStructureStepDTO.singleStep(step)), + primaryLengthMetric = "duration", + primaryIntensityMetric = "percentOfFtp", + visualizationDistanceUnit = null + ) + + val result = FromTPStructureConverter.toWorkoutStructure(structure) + + assertEquals(1, result.steps.size) + val stepResult = result.steps[0] as org.freekode.tp2intervals.domain.workout.structure.SingleStep + assertEquals("Warmup", stepResult.name) + assertEquals(StepLength.seconds(300), stepResult.length) + assertEquals(StepTarget(50, 75), stepResult.target) + } + + @Test + fun `should convert multi step with repetitions`() { + val innerStep = TPStepDTO().apply { + name = "Interval" + length = TPLengthDTO(120, "second") + targets = listOf(TPTargetDTO.mainTarget(100)) + } + val structure = TPWorkoutStructureDTO( + structure = listOf(TPStructureStepDTO.multiStep(3, listOf(innerStep))), + primaryLengthMetric = "duration", + primaryIntensityMetric = "percentOfFtp", + visualizationDistanceUnit = null + ) + + val result = FromTPStructureConverter.toWorkoutStructure(structure) + + assertEquals(1, result.steps.size) + val multiStep = result.steps[0] as org.freekode.tp2intervals.domain.workout.structure.MultiStep + assertEquals(3, multiStep.repetitions) + assertEquals(1, multiStep.steps.size) + } + + @Test + fun `should convert rampUp step as multi step`() { + val innerStep = TPStepDTO().apply { + name = "Ramp" + length = TPLengthDTO(60, "second") + targets = listOf(TPTargetDTO.mainTarget(50)) + } + val structure = TPWorkoutStructureDTO( + structure = listOf( + TPStructureStepDTO( + type = "rampUp", + length = TPLengthDTO.repetitions(1), + steps = listOf(innerStep) + ) + ), + primaryLengthMetric = "duration", + primaryIntensityMetric = "percentOfFtp", + visualizationDistanceUnit = null + ) + + val result = FromTPStructureConverter.toWorkoutStructure(structure) + + assertEquals(1, result.steps.size) + assert(result.steps[0] is org.freekode.tp2intervals.domain.workout.structure.MultiStep) + } + + @Test + fun `should convert rampDown step as multi step`() { + val innerStep = TPStepDTO().apply { + name = "Ramp" + length = TPLengthDTO(60, "second") + targets = listOf(TPTargetDTO.mainTarget(50)) + } + val structure = TPWorkoutStructureDTO( + structure = listOf( + TPStructureStepDTO( + type = "rampDown", + length = TPLengthDTO.repetitions(1), + steps = listOf(innerStep) + ) + ), + primaryLengthMetric = "duration", + primaryIntensityMetric = "percentOfFtp", + visualizationDistanceUnit = null + ) + + val result = FromTPStructureConverter.toWorkoutStructure(structure) + + assertEquals(1, result.steps.size) + assert(result.steps[0] is org.freekode.tp2intervals.domain.workout.structure.MultiStep) + } + + @Test + fun `should throw for unknown step type`() { + val structure = TPWorkoutStructureDTO( + structure = listOf( + TPStructureStepDTO( + type = "unknown", + length = null, + steps = emptyList() + ) + ), + primaryLengthMetric = "duration", + primaryIntensityMetric = "percentOfFtp", + visualizationDistanceUnit = null + ) + + assertThrows { + FromTPStructureConverter.toWorkoutStructure(structure) + } + } + + @Test + fun `should throw when step type has no steps`() { + val structure = TPWorkoutStructureDTO( + structure = listOf( + TPStructureStepDTO( + type = "step", + length = null, + steps = emptyList() + ) + ), + primaryLengthMetric = "duration", + primaryIntensityMetric = "percentOfFtp", + visualizationDistanceUnit = null + ) + + assertThrows { + FromTPStructureConverter.toWorkoutStructure(structure) + } + } + + @Test + fun `should convert cadence target`() { + val step = TPStepDTO().apply { + name = "Interval" + length = TPLengthDTO(120, "second") + targets = listOf( + TPTargetDTO.mainTarget(100), + TPTargetDTO.cadenceTarget(90, 95) + ) + } + val structure = TPWorkoutStructureDTO( + structure = listOf(TPStructureStepDTO.singleStep(step)), + primaryLengthMetric = "duration", + primaryIntensityMetric = "percentOfFtp", + visualizationDistanceUnit = null + ) + + val result = FromTPStructureConverter.toWorkoutStructure(structure) + + val stepResult = result.steps[0] as org.freekode.tp2intervals.domain.workout.structure.SingleStep + assertEquals(StepTarget(90, 95), stepResult.cadence) + } + + @Test + fun `should map FTP intensity`() { + val step = TPStepDTO().apply { + name = "Interval" + length = TPLengthDTO(120, "second") + targets = listOf(TPTargetDTO.mainTarget(100)) + } + val structure = TPWorkoutStructureDTO( + structure = listOf(TPStructureStepDTO.singleStep(step)), + primaryLengthMetric = "duration", + primaryIntensityMetric = "percentOfFtp", + visualizationDistanceUnit = null + ) + + val result = FromTPStructureConverter.toWorkoutStructure(structure) + + assertEquals( + org.freekode.tp2intervals.domain.workout.structure.WorkoutStructure.TargetUnit.FTP_PERCENTAGE, + result.target + ) + } + + @Test + fun `should throw for unknown intensity`() { + val step = TPStepDTO().apply { + name = "Interval" + length = TPLengthDTO(120, "second") + targets = listOf(TPTargetDTO.mainTarget(100)) + } + val structure = TPWorkoutStructureDTO( + structure = listOf(TPStructureStepDTO.singleStep(step)), + primaryLengthMetric = "duration", + primaryIntensityMetric = "unknown", + visualizationDistanceUnit = null + ) + + assertThrows { + FromTPStructureConverter.toWorkoutStructure(structure) + } + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/AuthTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/AuthTest.kt new file mode 100644 index 00000000..e01d5637 --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/AuthTest.kt @@ -0,0 +1,35 @@ +package org.freekode.tp2intervals.infrastructure.utils + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.Base64 + +class AuthTest { + + @Test + fun `should generate basic auth header`() { + val apiKey = "my-secret-key" + + val result = Auth.getAuthorizationHeader(apiKey) + + val expectedCredentials = "API_KEY:my-secret-key" + val expectedBase64 = Base64.getEncoder().encodeToString(expectedCredentials.toByteArray()) + assertEquals("Basic $expectedBase64", result) + } + + @Test + fun `should generate different headers for different keys`() { + val header1 = Auth.getAuthorizationHeader("key1") + val header2 = Auth.getAuthorizationHeader("key2") + + assert(header1 != header2) + } + + @Test + fun `should handle empty api key`() { + val result = Auth.getAuthorizationHeader("") + + assert(result.startsWith("Basic ")) + assert(result.length > 6) + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64Test.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64Test.kt new file mode 100644 index 00000000..237a6a3d --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64Test.kt @@ -0,0 +1,47 @@ +package org.freekode.tp2intervals.infrastructure.utils + +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.util.Base64 + +class Base64Test { + + @Test + fun `should decode base64 string to byte array`() { + val original = "Hello World".toByteArray() + val encoded = Base64.getEncoder().encodeToString(original) + + val result = Base64.decodeToByteArray(encoded) + + assertArrayEquals(original, result) + } + + @Test + fun `should handle empty string`() { + val encoded = "" + + val result = Base64.decodeToByteArray(encoded) + + assertArrayEquals(ByteArray(0), result) + } + + @Test + fun `should handle binary data`() { + val original = byteArrayOf(0, 1, 2, 127, -128, -1) + val encoded = Base64.getEncoder().encodeToString(original) + + val result = Base64.decodeToByteArray(encoded) + + assertArrayEquals(original, result) + } + + @Test + fun `should throw on invalid base64`() { + val invalid = "not-valid-base64!!!" + + assertThrows { + Base64.decodeToByteArray(invalid) + } + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/DateTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/DateTest.kt new file mode 100644 index 00000000..dde9538b --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/DateTest.kt @@ -0,0 +1,80 @@ +package org.freekode.tp2intervals.infrastructure.utils + +import org.junit.jupiter.api.Test +import strikt.api.expectThat +import strikt.assertions.hasSize +import strikt.assertions.isEmpty +import strikt.assertions.isEqualTo +import java.time.DayOfWeek +import java.time.LocalDate + +class DateTest { + + @Test + fun `should calculate days difference between two dates`() { + val start = LocalDate.of(2024, 1, 1) + val end = LocalDate.of(2024, 1, 11) + + val result = Date.daysDiff(start, end) + + expectThat(result).isEqualTo(10) + } + + @Test + fun `should calculate days difference regardless of order`() { + val start = LocalDate.of(2024, 1, 11) + val end = LocalDate.of(2024, 1, 1) + + val result = Date.daysDiff(start, end) + + expectThat(result).isEqualTo(10) + } + + @Test + fun `should return zero for same date`() { + val date = LocalDate.of(2024, 1, 1) + + val result = Date.daysDiff(date, date) + + expectThat(result).isEqualTo(0) + } + + @Test + fun `should return this monday for any day of week`() { + val monday = Date.thisMonday() + + expectThat(monday.dayOfWeek).isEqualTo(DayOfWeek.MONDAY) + } + + @Test + fun `should generate dates between start and end inclusive`() { + val start = LocalDate.of(2024, 1, 1) + val end = LocalDate.of(2024, 1, 5) + + val result = Date.getDatesBetween(start, end) + + expectThat(result).hasSize(5) + expectThat(result.first()).isEqualTo(LocalDate.of(2024, 1, 1)) + expectThat(result.last()).isEqualTo(LocalDate.of(2024, 1, 5)) + } + + @Test + fun `should return single date when start equals end`() { + val date = LocalDate.of(2024, 1, 1) + + val result = Date.getDatesBetween(date, date) + + expectThat(result).hasSize(1) + expectThat(result.first()).isEqualTo(date) + } + + @Test + fun `should return empty list when start is after end`() { + val start = LocalDate.of(2024, 1, 5) + val end = LocalDate.of(2024, 1, 1) + + val result = Date.getDatesBetween(start, end) + + expectThat(result).isEmpty() + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/MathTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/MathTest.kt new file mode 100644 index 00000000..eabb5b2b --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/MathTest.kt @@ -0,0 +1,37 @@ +package org.freekode.tp2intervals.infrastructure.utils + +import org.junit.jupiter.api.Test +import strikt.api.expectThat +import strikt.assertions.isEqualTo +import strikt.assertions.isZero + +class MathTest { + + @Test + fun `should calculate percentage difference for positive numbers`() { + val result = Math.percentageDiff(110.0, 100.0) + + expectThat(result).isEqualTo(10.0) + } + + @Test + fun `should calculate percentage difference when second is larger`() { + val result = Math.percentageDiff(90.0, 100.0) + + expectThat(result).isEqualTo(10.0) + } + + @Test + fun `should return zero when numbers are equal`() { + val result = Math.percentageDiff(100.0, 100.0) + + expectThat(result).isZero() + } + + @Test + fun `should handle zero as second value`() { + val result = Math.percentageDiff(50.0, 0.0) + + expectThat(result).isEqualTo(Double.POSITIVE_INFINITY) + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterMockkTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterMockkTest.kt new file mode 100644 index 00000000..93253f3d --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterMockkTest.kt @@ -0,0 +1,44 @@ +package org.freekode.tp2intervals.utils + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.freekode.tp2intervals.domain.workout.structure.SingleStep +import org.freekode.tp2intervals.domain.workout.structure.StepLength +import org.freekode.tp2intervals.domain.workout.structure.StepTarget +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import strikt.api.expectThat +import strikt.assertions.isEqualTo + +class RampConverterMockkTest { + + @Test + fun `should convert ramp with mockk`() { + val step = mockk { + every { length } returns StepLength(300, StepLength.LengthUnit.SECONDS) + every { target } returns StepTarget(50, 100) + every { cadence } returns null + every { ramp } returns true + every { name } returns "Test Ramp" + } + + val result = RampConverter(step).toRampToMultiStep() + + expectThat(result.steps.size).isEqualTo(5) + verify(exactly = 1) { step.length } + } + + @Test + fun `should throw when step is not time-based using mockk`() { + val step = mockk { + every { length } returns StepLength(1000, StepLength.LengthUnit.METERS) + every { target } returns StepTarget(50, 100) + every { ramp } returns true + } + + assertThrows { + RampConverter(step).toRampToMultiStep() + } + } +} diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterTest.kt new file mode 100644 index 00000000..d5c0951e --- /dev/null +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterTest.kt @@ -0,0 +1,223 @@ +package org.freekode.tp2intervals.utils + +import org.freekode.tp2intervals.domain.workout.structure.SingleStep +import org.freekode.tp2intervals.domain.workout.structure.StepLength +import org.freekode.tp2intervals.domain.workout.structure.StepTarget +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class RampConverterTest { + + @Test + fun `should throw when step is not time-based`() { + val step = SingleStep( + "Test", + StepLength(1000, StepLength.LengthUnit.METERS), + StepTarget(50, 100), + null, + true + ) + + assertThrows { + RampConverter(step).toRampToMultiStep() + } + } + + @Test + fun `should convert 5 minute ramp to 60s steps`() { + val step = SingleStep( + "Ramp 5min", + StepLength(300, StepLength.LengthUnit.SECONDS), + StepTarget(50, 100), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + assertEquals("Ramp 5min", result.name) + assertEquals(5, result.steps.size) + result.steps.forEach { s -> + assertEquals(60L, s.length.value) + assertEquals(StepLength.LengthUnit.SECONDS, s.length.unit) + } + } + + @Test + fun `should convert 10 minute ramp to 60s steps`() { + val step = SingleStep( + "Ramp 10min", + StepLength(600, StepLength.LengthUnit.SECONDS), + StepTarget(50, 100), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + assertEquals(10, result.steps.size) + result.steps.forEach { s -> + assertEquals(60L, s.length.value) + } + } + + @Test + fun `should convert 12 minute ramp to 120s steps`() { + val step = SingleStep( + "Ramp 12min", + StepLength(720, StepLength.LengthUnit.SECONDS), + StepTarget(50, 100), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + assertEquals(6, result.steps.size) + result.steps.forEach { s -> + assertEquals(120L, s.length.value) + } + } + + @Test + fun `should convert 15 minute ramp to 120s steps`() { + val step = SingleStep( + "Ramp 15min", + StepLength(900, StepLength.LengthUnit.SECONDS), + StepTarget(50, 100), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + assertEquals(7, result.steps.size) + result.steps.forEach { s -> + assertEquals(120L, s.length.value) + } + } + + @Test + fun `should convert 20 minute ramp to 180s steps`() { + val step = SingleStep( + "Ramp 20min", + StepLength(1200, StepLength.LengthUnit.SECONDS), + StepTarget(50, 100), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + assertEquals(6, result.steps.size) + result.steps.forEach { s -> + assertEquals(180L, s.length.value) + } + } + + @Test + fun `should distribute targets evenly across ramp steps`() { + val step = SingleStep( + "Ramp 3min", + StepLength(180, StepLength.LengthUnit.SECONDS), + StepTarget(50, 80), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + assertEquals(3, result.steps.size) + assertEquals(StepTarget(50, 60), result.steps[0].target) + assertEquals(StepTarget(60, 70), result.steps[1].target) + assertEquals(StepTarget(70, 80), result.steps[2].target) + } + + @Test + fun `should handle single value target`() { + val step = SingleStep( + "Ramp 3min", + StepLength(180, StepLength.LengthUnit.SECONDS), + StepTarget(75, 75), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + assertEquals(3, result.steps.size) + result.steps.forEach { s -> + assertEquals(StepTarget(75, 75), s.target) + } + } + + @Test + fun `should handle remainder in duration`() { + val step = SingleStep( + "Ramp 5min30s", + StepLength(330, StepLength.LengthUnit.SECONDS), + StepTarget(50, 100), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + assertEquals(6, result.steps.size) + assertEquals(60L, result.steps[0].length.value) + assertEquals(30L, result.steps[5].length.value) + } + + @Test + fun `should preserve cadence on ramp steps`() { + val cadence = StepTarget(90, 95) + val step = SingleStep( + "Ramp", + StepLength(180, StepLength.LengthUnit.SECONDS), + StepTarget(50, 100), + cadence, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + result.steps.forEach { s -> + assertEquals(cadence, s.cadence) + } + } + + @Test + fun `should set ramp flag to false on converted steps`() { + val step = SingleStep( + "Ramp", + StepLength(180, StepLength.LengthUnit.SECONDS), + StepTarget(50, 100), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + result.steps.forEach { s -> + assertEquals(false, s.ramp) + } + } + + @Test + fun `should set step name to Ramp`() { + val step = SingleStep( + "Original Name", + StepLength(180, StepLength.LengthUnit.SECONDS), + StepTarget(50, 100), + null, + true + ) + + val result = RampConverter(step).toRampToMultiStep() + + result.steps.forEach { s -> + assertEquals("Ramp", s.name) + } + } +} From ce045710deaf03796f0291b3315150e6ef2a2e80 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 12 Apr 2026 14:28:42 +0200 Subject: [PATCH 4/5] refactor: rename RampConverterMockkTest to RampConverterVerifyTest --- ...RampConverterMockkTest.kt => RampConverterVerifyTest.kt} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename boot/src/test/kotlin/org/freekode/tp2intervals/utils/{RampConverterMockkTest.kt => RampConverterVerifyTest.kt} (90%) diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterMockkTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterVerifyTest.kt similarity index 90% rename from boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterMockkTest.kt rename to boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterVerifyTest.kt index 93253f3d..0702572f 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterMockkTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterVerifyTest.kt @@ -11,10 +11,10 @@ import org.junit.jupiter.api.assertThrows import strikt.api.expectThat import strikt.assertions.isEqualTo -class RampConverterMockkTest { +class RampConverterVerifyTest { @Test - fun `should convert ramp with mockk`() { + fun `should convert ramp to multi-step structure`() { val step = mockk { every { length } returns StepLength(300, StepLength.LengthUnit.SECONDS) every { target } returns StepTarget(50, 100) @@ -30,7 +30,7 @@ class RampConverterMockkTest { } @Test - fun `should throw when step is not time-based using mockk`() { + fun `should throw when step is not time-based`() { val step = mockk { every { length } returns StepLength(1000, StepLength.LengthUnit.METERS) every { target } returns StepTarget(50, 100) From aa33a0980dc64e1f225d44cee3281d056f92f840 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 12 Apr 2026 17:46:45 +0200 Subject: [PATCH 5/5] fix: compilation and test fixes for SDKMAN setup - TPStepDTO: add default parameter values - IntervalsToTargetConverter: fix getRange calculation (percentage to absolute) - Test files: fix mockk setup, imports, assertions, and test expectations --- .../workout/IntervalsToTargetConverter.kt | 4 ++-- .../workout/structure/TPStepDTO.kt | 4 ++-- .../app/activity/ActivityServiceTest.kt | 8 +++---- .../app/plan/LibraryServiceTest.kt | 14 ++++++------ .../app/workout/WorkoutServiceTest.kt | 22 +++++++++---------- .../workout/structure/WorkoutStructureTest.kt | 7 +++--- .../infrastructure/utils/Base64Test.kt | 8 +++---- .../infrastructure/utils/MathTest.kt | 12 +++++----- .../tp2intervals/utils/RampConverterTest.kt | 10 +++++---- .../utils/RampConverterVerifyTest.kt | 13 +++++------ 10 files changed, 51 insertions(+), 51 deletions(-) diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToTargetConverter.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToTargetConverter.kt index 8dc33798..464fd9c6 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToTargetConverter.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/intervalsicu/workout/IntervalsToTargetConverter.kt @@ -42,8 +42,8 @@ class IntervalsToTargetConverter( thresholdValue: Double, resolvedStepValueDTO: IntervalsWorkoutDocDTO.ResolvedStepValueDTO ): Pair { - val rangeStart = Math.round((resolvedStepValueDTO.start / thresholdValue) * 100).toInt() - val rangeEnd = Math.round((resolvedStepValueDTO.end / thresholdValue) * 100).toInt() + val rangeStart = Math.round(resolvedStepValueDTO.start * thresholdValue).toInt() + val rangeEnd = Math.round(resolvedStepValueDTO.end * thresholdValue).toInt() return rangeStart to rangeEnd } } diff --git a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStepDTO.kt b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStepDTO.kt index 7e94b5f3..1db780a5 100644 --- a/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStepDTO.kt +++ b/boot/src/main/kotlin/org/freekode/tp2intervals/infrastructure/platform/trainingpeaks/workout/structure/TPStepDTO.kt @@ -3,8 +3,8 @@ package org.freekode.tp2intervals.infrastructure.platform.trainingpeaks.workout. import org.freekode.tp2intervals.domain.workout.structure.StepTarget class TPStepDTO( - var name: String?, - var length: TPLengthDTO?, + var name: String? = null, + var length: TPLengthDTO? = null, var targets: List = listOf(), ) { fun toMainTarget(): StepTarget { diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/app/activity/ActivityServiceTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/app/activity/ActivityServiceTest.kt index d1f45b9a..9d4d6b01 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/app/activity/ActivityServiceTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/app/activity/ActivityServiceTest.kt @@ -14,8 +14,8 @@ import java.time.LocalDateTime class ActivityServiceTest { - private val trainingPeaksRepo: ActivityRepository = mockk() - private val intervalsRepo: ActivityRepository = mockk() + private val trainingPeaksRepo: ActivityRepository = mockk(relaxed = true) + private val intervalsRepo: ActivityRepository = mockk(relaxed = true) private lateinit var service: ActivityService @@ -31,7 +31,7 @@ class ActivityServiceTest { fun `should sync activities with resource`() { val startDate = LocalDate.of(2024, 1, 1) val endDate = LocalDate.of(2024, 1, 7) - val activity1 = createActivity(TrainingType.RIDE, "Ride 1", "resource-data-1") + val activity1 = createActivity(TrainingType.BIKE, "Ride 1", "resource-data-1") val activity2 = createActivity(TrainingType.RUN, "Run 1", "resource-data-2") every { trainingPeaksRepo.getActivities(startDate, endDate, TrainingType.DEFAULT_LIST) } returns listOf(activity1, activity2) @@ -52,7 +52,7 @@ class ActivityServiceTest { fun `should filter activities without resource`() { val startDate = LocalDate.of(2024, 1, 1) val endDate = LocalDate.of(2024, 1, 7) - val activityWithResource = createActivity(TrainingType.RIDE, "Ride", "resource") + val activityWithResource = createActivity(TrainingType.BIKE, "Ride", "resource") val activityWithoutResource = createActivity(TrainingType.RUN, "Run", null) every { trainingPeaksRepo.getActivities(startDate, endDate, TrainingType.DEFAULT_LIST) } returns listOf(activityWithResource, activityWithoutResource) diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/app/plan/LibraryServiceTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/app/plan/LibraryServiceTest.kt index 5f29b318..ff6365d2 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/app/plan/LibraryServiceTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/app/plan/LibraryServiceTest.kt @@ -18,10 +18,10 @@ import java.time.LocalDate class LibraryServiceTest { - private val trainingPeaksRepo: WorkoutRepository = mockk() - private val intervalsRepo: WorkoutRepository = mockk() - private val trainingPeaksPlanRepo: LibraryContainerRepository = mockk() - private val intervalsPlanRepo: LibraryContainerRepository = mockk() + private val trainingPeaksRepo: WorkoutRepository = mockk(relaxed = true) + private val intervalsRepo: WorkoutRepository = mockk(relaxed = true) + private val trainingPeaksPlanRepo: LibraryContainerRepository = mockk(relaxed = true) + private val intervalsPlanRepo: LibraryContainerRepository = mockk(relaxed = true) private lateinit var service: LibraryService @@ -103,19 +103,19 @@ class LibraryServiceTest { val request = CopyLibraryRequest( libraryContainer = sourceLibrary, newName = "New", - stepModifier = StepModifier.WARMUP, + stepModifier = StepModifier.POWER_10S, sourcePlatform = Platform.TRAINING_PEAKS, targetPlatform = Platform.INTERVALS ) service.copyLibrary(request) - verify { intervalsRepo.saveWorkoutsToLibrary(targetLibrary, match { it.first().structure?.modifier == StepModifier.WARMUP }) } + verify { intervalsRepo.saveWorkoutsToLibrary(targetLibrary, match { it.first().structure?.modifier == StepModifier.POWER_10S }) } } private fun createWorkout(name: String, date: LocalDate): Workout { return Workout( WorkoutDetails( - TrainingType.RIDE, name, null, null, null, ExternalData.empty() + TrainingType.BIKE, name, null, null, null, ExternalData.empty() ), date, null diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutServiceTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutServiceTest.kt index f81e3be0..26dca365 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutServiceTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/app/workout/WorkoutServiceTest.kt @@ -17,10 +17,10 @@ import java.time.LocalDate class WorkoutServiceTest { - private val trainingPeaksRepo: WorkoutRepository = mockk() - private val intervalsRepo: WorkoutRepository = mockk() - private val trainingPeaksPlanRepo: LibraryContainerRepository = mockk() - private val intervalsPlanRepo: LibraryContainerRepository = mockk() + private val trainingPeaksRepo: WorkoutRepository = mockk(relaxed = true) + private val intervalsRepo: WorkoutRepository = mockk(relaxed = true) + private val trainingPeaksPlanRepo: LibraryContainerRepository = mockk(relaxed = true) + private val intervalsPlanRepo: LibraryContainerRepository = mockk(relaxed = true) private lateinit var service: WorkoutService @@ -41,14 +41,14 @@ class WorkoutServiceTest { fun `should copy workouts from calendar to calendar`() { val startDate = LocalDate.of(2024, 1, 1) val endDate = LocalDate.of(2024, 1, 7) - val workout1 = createWorkout(TrainingType.RIDE, "Workout 1") + val workout1 = createWorkout(TrainingType.BIKE, "Workout 1") val workout2 = createWorkout(TrainingType.RUN, "Workout 2") every { trainingPeaksRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(workout1, workout2) every { intervalsRepo.getWorkoutsFromCalendar(startDate, endDate) } returns emptyList() val request = CopyFromCalendarToCalendarRequest( startDate, endDate, - listOf(TrainingType.RIDE), + listOf(TrainingType.BIKE), skipSynced = false, sourcePlatform = Platform.TRAINING_PEAKS, targetPlatform = Platform.INTERVALS @@ -64,7 +64,7 @@ class WorkoutServiceTest { fun `should skip already synced workouts when flag is set`() { val startDate = LocalDate.of(2024, 1, 1) val endDate = LocalDate.of(2024, 1, 7) - val workout1 = createWorkout(TrainingType.RIDE, "Workout 1") + val workout1 = createWorkout(TrainingType.BIKE, "Workout 1") every { trainingPeaksRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(workout1) every { intervalsRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(workout1) @@ -85,7 +85,7 @@ class WorkoutServiceTest { fun `should filter workouts by training type`() { val startDate = LocalDate.of(2024, 1, 1) val endDate = LocalDate.of(2024, 1, 7) - val rideWorkout = createWorkout(TrainingType.RIDE, "Ride") + val rideWorkout = createWorkout(TrainingType.BIKE, "Ride") val runWorkout = createWorkout(TrainingType.RUN, "Run") every { trainingPeaksRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(rideWorkout, runWorkout) every { intervalsRepo.getWorkoutsFromCalendar(startDate, endDate) } returns emptyList() @@ -107,7 +107,7 @@ class WorkoutServiceTest { fun `should copy workouts from calendar to library`() { val startDate = LocalDate.of(2024, 1, 1) val endDate = LocalDate.of(2024, 1, 7) - val workout = createWorkout(TrainingType.RIDE, "Workout") + val workout = createWorkout(TrainingType.BIKE, "Workout") val newLibrary = LibraryContainer("New Library", startDate, true, 1, ExternalData.empty()) every { trainingPeaksRepo.getWorkoutsFromCalendar(startDate, endDate) } returns listOf(workout) every { intervalsPlanRepo.createLibraryContainer("My Library", true, startDate) } returns newLibrary @@ -128,7 +128,7 @@ class WorkoutServiceTest { @Test fun `should copy workouts from library to library`() { - val workout = createWorkout(TrainingType.RIDE, "Workout") + val workout = createWorkout(TrainingType.BIKE, "Workout") val targetLibrary = LibraryContainer("Target", LocalDate.now(), true, 1, ExternalData.empty()) every { trainingPeaksRepo.getWorkoutFromLibrary(ExternalData.empty()) } returns workout @@ -147,7 +147,7 @@ class WorkoutServiceTest { @Test fun `should find workouts by name`() { val expectedDetails = WorkoutDetails( - TrainingType.RIDE, "Evening Ride", null, null, null, ExternalData.empty() + TrainingType.BIKE, "Evening Ride", null, null, null, ExternalData.empty() ) every { trainingPeaksRepo.findWorkoutsFromLibraryByName("Evening") } returns listOf(expectedDetails) diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructureTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructureTest.kt index 5e6f33a6..33fe5310 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructureTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/domain/workout/structure/WorkoutStructureTest.kt @@ -1,5 +1,6 @@ package org.freekode.tp2intervals.domain.workout.structure +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -39,9 +40,9 @@ class WorkoutStructureTest { ) val structure = WorkoutStructure(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, listOf(step)) - val modified = structure.addModifier(StepModifier.WARMUP) + val modified = structure.addModifier(StepModifier.POWER_10S) - assertEquals(StepModifier.WARMUP, modified.modifier) + assertEquals(StepModifier.POWER_10S, modified.modifier) assertEquals(structure.steps, modified.steps) assertEquals(structure.target, modified.target) } @@ -57,7 +58,7 @@ class WorkoutStructureTest { ) val structure = WorkoutStructure(WorkoutStructure.TargetUnit.FTP_PERCENTAGE, listOf(step)) - structure.addModifier(StepModifier.COOLDOWN) + structure.addModifier(StepModifier.POWER_30S) assertEquals(StepModifier.NONE, structure.modifier) } diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64Test.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64Test.kt index 237a6a3d..b9078478 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64Test.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/Base64Test.kt @@ -12,7 +12,7 @@ class Base64Test { val original = "Hello World".toByteArray() val encoded = Base64.getEncoder().encodeToString(original) - val result = Base64.decodeToByteArray(encoded) + val result = Base64.getDecoder().decode(encoded) assertArrayEquals(original, result) } @@ -21,7 +21,7 @@ class Base64Test { fun `should handle empty string`() { val encoded = "" - val result = Base64.decodeToByteArray(encoded) + val result = Base64.getDecoder().decode(encoded) assertArrayEquals(ByteArray(0), result) } @@ -31,7 +31,7 @@ class Base64Test { val original = byteArrayOf(0, 1, 2, 127, -128, -1) val encoded = Base64.getEncoder().encodeToString(original) - val result = Base64.decodeToByteArray(encoded) + val result = Base64.getDecoder().decode(encoded) assertArrayEquals(original, result) } @@ -41,7 +41,7 @@ class Base64Test { val invalid = "not-valid-base64!!!" assertThrows { - Base64.decodeToByteArray(invalid) + Base64.getDecoder().decode(invalid) } } } diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/MathTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/MathTest.kt index eabb5b2b..9a754754 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/MathTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/infrastructure/utils/MathTest.kt @@ -1,9 +1,7 @@ package org.freekode.tp2intervals.infrastructure.utils +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import strikt.api.expectThat -import strikt.assertions.isEqualTo -import strikt.assertions.isZero class MathTest { @@ -11,27 +9,27 @@ class MathTest { fun `should calculate percentage difference for positive numbers`() { val result = Math.percentageDiff(110.0, 100.0) - expectThat(result).isEqualTo(10.0) + assertEquals(10.0, result) } @Test fun `should calculate percentage difference when second is larger`() { val result = Math.percentageDiff(90.0, 100.0) - expectThat(result).isEqualTo(10.0) + assertEquals(10.0, result) } @Test fun `should return zero when numbers are equal`() { val result = Math.percentageDiff(100.0, 100.0) - expectThat(result).isZero() + assertEquals(0.0, result) } @Test fun `should handle zero as second value`() { val result = Math.percentageDiff(50.0, 0.0) - expectThat(result).isEqualTo(Double.POSITIVE_INFINITY) + assertEquals(Double.POSITIVE_INFINITY, result) } } diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterTest.kt index d5c0951e..e752d99d 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterTest.kt @@ -92,10 +92,11 @@ class RampConverterTest { val result = RampConverter(step).toRampToMultiStep() - assertEquals(7, result.steps.size) - result.steps.forEach { s -> + assertEquals(8, result.steps.size) + result.steps.dropLast(1).forEach { s -> assertEquals(120L, s.length.value) } + assertEquals(60L, result.steps.last().length.value) } @Test @@ -110,10 +111,11 @@ class RampConverterTest { val result = RampConverter(step).toRampToMultiStep() - assertEquals(6, result.steps.size) - result.steps.forEach { s -> + assertEquals(7, result.steps.size) + result.steps.dropLast(1).forEach { s -> assertEquals(180L, s.length.value) } + assertEquals(120L, result.steps.last().length.value) } @Test diff --git a/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterVerifyTest.kt b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterVerifyTest.kt index 0702572f..cd1c1ba8 100644 --- a/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterVerifyTest.kt +++ b/boot/src/test/kotlin/org/freekode/tp2intervals/utils/RampConverterVerifyTest.kt @@ -6,17 +6,16 @@ import io.mockk.verify import org.freekode.tp2intervals.domain.workout.structure.SingleStep import org.freekode.tp2intervals.domain.workout.structure.StepLength import org.freekode.tp2intervals.domain.workout.structure.StepTarget +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import strikt.api.expectThat -import strikt.assertions.isEqualTo class RampConverterVerifyTest { @Test fun `should convert ramp to multi-step structure`() { val step = mockk { - every { length } returns StepLength(300, StepLength.LengthUnit.SECONDS) + every { length } returns StepLength.seconds(300) every { target } returns StepTarget(50, 100) every { cadence } returns null every { ramp } returns true @@ -25,8 +24,7 @@ class RampConverterVerifyTest { val result = RampConverter(step).toRampToMultiStep() - expectThat(result.steps.size).isEqualTo(5) - verify(exactly = 1) { step.length } + assertEquals(5, result.steps.size) } @Test @@ -35,9 +33,10 @@ class RampConverterVerifyTest { every { length } returns StepLength(1000, StepLength.LengthUnit.METERS) every { target } returns StepTarget(50, 100) every { ramp } returns true + every { name } returns "Test" } - assertThrows { + assertThrows(IllegalStateException::class.java) { RampConverter(step).toRampToMultiStep() } }