diff --git a/src/main/java/com/kntro/reqsai/discovery/domain/exception/DiscoveryExceptions.java b/src/main/java/com/kntro/reqsai/discovery/domain/exception/DiscoveryExceptions.java index 9b0c028f..74c72ded 100644 --- a/src/main/java/com/kntro/reqsai/discovery/domain/exception/DiscoveryExceptions.java +++ b/src/main/java/com/kntro/reqsai/discovery/domain/exception/DiscoveryExceptions.java @@ -4,6 +4,8 @@ import com.kntro.reqsai.shared.domain.exception.DomainException; import com.kntro.reqsai.shared.domain.exception.EntityNotFoundException; +import java.util.Locale; + /** * Factory for Requirement Discovery domain exceptions — the context-specific counterpart of the shared * {@code Exceptions} and a mirror of {@code WorkspaceExceptions}. @@ -15,8 +17,11 @@ private DiscoveryExceptions() { } public static DomainException duplicateUserStory(double similarity) { + // Format with Locale.ROOT so the decimal separator is always a dot: the client parses this + // similarity out of the ProblemDetail `detail` expecting "0.87", and a comma-decimal JVM locale + // would otherwise make it read the score as 0 (0%) on the duplicate-story surface. return new DomainException(DiscoveryError.DUPLICATE_USER_STORY, - "A near-duplicate user story already exists (similarity %.2f)".formatted(similarity)); + String.format(Locale.ROOT, "A near-duplicate user story already exists (similarity %.2f)", similarity)); } public static EntityNotFoundException sessionNotFound(java.util.UUID id) { diff --git a/src/test/java/com/kntro/reqsai/discovery/domain/exception/DiscoveryExceptionsTest.java b/src/test/java/com/kntro/reqsai/discovery/domain/exception/DiscoveryExceptionsTest.java new file mode 100644 index 00000000..e7f23b6f --- /dev/null +++ b/src/test/java/com/kntro/reqsai/discovery/domain/exception/DiscoveryExceptionsTest.java @@ -0,0 +1,34 @@ +package com.kntro.reqsai.discovery.domain.exception; + +import com.kntro.reqsai.shared.domain.exception.DomainException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Locale; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("Discovery: duplicate-story exception") +class DiscoveryExceptionsTest { + + private final Locale original = Locale.getDefault(); + + @AfterEach + void restoreLocale() { + Locale.setDefault(original); + } + + @Test + @DisplayName("similarity is dot-formatted even under a comma-decimal default locale") + void similarity_dot_formatted_regardless_of_locale() { + // A comma-decimal locale (es-PE/Germany) would otherwise render "0,87", which the client parses + // as 0 (0%) off the ProblemDetail detail. Locale.ROOT keeps the machine-parsed value a dot. + Locale.setDefault(Locale.GERMANY); + + DomainException ex = DiscoveryExceptions.duplicateUserStory(0.87); + + assertThat(ex.getMessage()).contains("similarity 0.87"); + assertThat(ex.getMessage()).doesNotContain("0,87"); + } +}