diff --git a/Report.docx b/Report.docx new file mode 100644 index 0000000..f6e38fd Binary files /dev/null and b/Report.docx differ diff --git a/jackson-datatype-problem/src/main/java/module-info.java b/jackson-datatype-problem/src/main/java/module-info.java index 3af1cac..3f37a13 100644 --- a/jackson-datatype-problem/src/main/java/module-info.java +++ b/jackson-datatype-problem/src/main/java/module-info.java @@ -4,6 +4,7 @@ requires transitive com.fasterxml.jackson.core; requires transitive com.fasterxml.jackson.databind; requires transitive org.zalando.problem; + requires org.checkerframework.checker.qual; exports org.zalando.problem.jackson; provides com.fasterxml.jackson.databind.Module with org.zalando.problem.jackson.ProblemModule; } diff --git a/jackson-datatype-problem/src/test/java/org/zalando/problem/jackson/MyTest.java b/jackson-datatype-problem/src/test/java/org/zalando/problem/jackson/MyTest.java new file mode 100644 index 0000000..f86150e --- /dev/null +++ b/jackson-datatype-problem/src/test/java/org/zalando/problem/jackson/MyTest.java @@ -0,0 +1,116 @@ +package org.zalando.problem.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.zalando.problem.Status; +import org.zalando.problem.StatusType; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +public class MyTest { + + @Test + @DisplayName("11.1 Input Status Code is in the Map") + void TestDeserializeWithKnownStatusCode() throws IOException { + Map statusMap = new HashMap<>(); + statusMap.put(400, Status.BAD_REQUEST); + + StatusTypeDeserializer deserializer = new StatusTypeDeserializer(statusMap); + ObjectMapper mapper = new ObjectMapper(); + + SimpleModule module = new SimpleModule(); + module.addDeserializer(StatusType.class, deserializer); + mapper.registerModule(module); + + StatusType result = mapper.readValue("400", StatusType.class); + + assertEquals(Status.BAD_REQUEST, result); + assertEquals(400, result.getStatusCode()); + assertEquals("Bad Request", result.getReasonPhrase()); + } + + @Test + @DisplayName("11.2 Multiple Input Status Code is in the Map") + void TestDeserializeWithMultipleKnownStatusCodes() throws IOException { + Map statusMap = new HashMap<>(); + statusMap.put(200, Status.OK); + statusMap.put(402, Status.PAYMENT_REQUIRED); + statusMap.put(500, Status.INTERNAL_SERVER_ERROR); + + StatusTypeDeserializer deserializer = new StatusTypeDeserializer(statusMap); + ObjectMapper mapper = new ObjectMapper(); + + SimpleModule module = new SimpleModule(); + module.addDeserializer(StatusType.class, deserializer); + mapper.registerModule(module); + + StatusType result200 = mapper.readValue("200", StatusType.class); + assertEquals(Status.OK, result200); + assertEquals(200, result200.getStatusCode()); + assertEquals("OK", result200.getReasonPhrase()); + + StatusType result402 = mapper.readValue("402", StatusType.class); + assertEquals(Status.PAYMENT_REQUIRED, result402); + assertEquals(402, result402.getStatusCode()); + assertEquals("Payment Required", result402.getReasonPhrase()); + + StatusType result500 = mapper.readValue("500", StatusType.class); + assertEquals(Status.INTERNAL_SERVER_ERROR, result500); + assertEquals(500, result500.getStatusCode()); + assertEquals("Internal Server Error", result500.getReasonPhrase()); + } + + + @Test + @DisplayName("11.3 Input Status Code is not in the Map") + void TestDeserializeWithUnknownStatusCode() throws IOException { + Map statusMap = new HashMap<>(); + statusMap.put(400, Status.BAD_REQUEST); + + StatusTypeDeserializer deserializer = new StatusTypeDeserializer(statusMap); + ObjectMapper mapper = new ObjectMapper(); + + SimpleModule module = new SimpleModule(); + module.addDeserializer(StatusType.class, deserializer); + mapper.registerModule(module); + + StatusType result1 = mapper.readValue("404", StatusType.class); + StatusType result2 = mapper.readValue("500", StatusType.class); + + assertEquals(404, result1.getStatusCode()); + assertEquals("Unknown", result1.getReasonPhrase()); + assertInstanceOf(UnknownStatus.class, result1); + assertEquals(500, result2.getStatusCode()); + assertEquals("Unknown", result2.getReasonPhrase()); + assertInstanceOf(UnknownStatus.class, result2); + } + + @Test + @DisplayName("11.4 The Map is empty") + void TestDeserializeWithEmptyMap() throws IOException { + Map emptyMap = new HashMap<>(); + + StatusTypeDeserializer deserializer = new StatusTypeDeserializer(emptyMap); + ObjectMapper mapper = new ObjectMapper(); + + SimpleModule module = new SimpleModule(); + module.addDeserializer(StatusType.class, deserializer); + mapper.registerModule(module); + + StatusType result = mapper.readValue("200", StatusType.class); + + assertEquals(200, result.getStatusCode()); + assertEquals("Unknown", result.getReasonPhrase()); + assertInstanceOf(UnknownStatus.class, result); + } +} diff --git a/pom.xml b/pom.xml index bd4b959..fb3a037 100644 --- a/pom.xml +++ b/pom.xml @@ -47,8 +47,8 @@ UTF-8 - 1.8 - 1.8 + 23 + 23 5.8.2 diff --git a/problem/pom.xml b/problem/pom.xml index 5ffc6bf..70f8882 100644 --- a/problem/pom.xml +++ b/problem/pom.xml @@ -10,4 +10,12 @@ problem An implementation of the application/problem+json draft. + + + com.google.code.gson + gson + 2.9.0 + compile + + diff --git a/problem/src/main/java/module-info.java b/problem/src/main/java/module-info.java index 583b7a0..6db67f7 100644 --- a/problem/src/main/java/module-info.java +++ b/problem/src/main/java/module-info.java @@ -2,4 +2,6 @@ requires static org.apiguardian.api; requires transitive com.google.gson; exports org.zalando.problem; + requires org.checkerframework.checker.qual; + uses org.zalando.problem.spi.StackTraceProcessor; } diff --git a/problem/src/test/java/org/zalando/problem/MyTest.java b/problem/src/test/java/org/zalando/problem/MyTest.java new file mode 100644 index 0000000..7364657 --- /dev/null +++ b/problem/src/test/java/org/zalando/problem/MyTest.java @@ -0,0 +1,951 @@ +package org.zalando.problem; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import javax.xml.crypto.Data; +import java.net.URI; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class MyTest { + @Nested + class TestCase_1_DefaultProblem_Creation { + + private static class TestStatus implements StatusType { + @Override + public int getStatusCode() { + return 888; + } + + @Override + public String getReasonPhrase() { + return "hello"; + } + } + + private static final URI VALID_TYPE = URI.create("https://example.org/problems/test"); + private static final URI VALID_INSTANCE = URI.create("https://example.org/problems/instances/123"); + private static final String VALID_TITLE = "Test Problem"; + private static final String VALID_DETAIL = "This is a test problem detail"; + private static final StatusType STATUS = new TestStatus(); + private static final ThrowableProblem CAUSE = new DefaultProblem( + URI.create("https://example.org/problems/cause"), + "Cause Problem", + null, + "This is the cause", + null, + null); + Map PARAMETERS = new HashMap<>() {{ + put("stringParam", "value"); + put("intParam", 42); + put("boolParam", true); + }}; + + + @Test + @DisplayName("1.01 Test DefaultProblem with all null") + public void DefaultProblemWithNull() { + DefaultProblem problem = new DefaultProblem( + null, null, null, null, null, null, null); + + assertEquals(Problem.DEFAULT_TYPE, problem.getType()); + assertNull(problem.getTitle()); + assertNull(problem.getStatus()); + assertNull(problem.getDetail()); + assertNull(problem.getInstance()); + assertNull(problem.getCause()); + assertTrue(problem.getParameters().isEmpty()); + } + + @Test + @DisplayName("1.02 Test DefaultProblem with all valid values") + public void DefaultProblemWithAllValid() { + DefaultProblem problem = new DefaultProblem( + VALID_TYPE, + VALID_TITLE, + STATUS, + VALID_DETAIL, + VALID_INSTANCE, + CAUSE, + PARAMETERS); + + assertEquals(VALID_TYPE, problem.getType()); + assertEquals(VALID_TITLE, problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertEquals(VALID_DETAIL, problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertEquals(CAUSE, problem.getCause()); + assertEquals(PARAMETERS, problem.getParameters()); + } + + @Test + @DisplayName("1.03 Test DefaultProblem with one null type values") + public void DefaultProblemWithTypeNull() { + DefaultProblem problem = new DefaultProblem( + null, + VALID_TITLE, + STATUS, + VALID_DETAIL, + VALID_INSTANCE, + CAUSE, + PARAMETERS); + + assertEquals(Problem.DEFAULT_TYPE, problem.getType()); + assertEquals(VALID_TITLE, problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertEquals(VALID_DETAIL, problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertEquals(CAUSE, problem.getCause()); + assertEquals(PARAMETERS, problem.getParameters()); + } + + @Test + @DisplayName("1.04 Test DefaultProblem with one null title values") + public void DefaultProblemWithTitleNull() { + DefaultProblem problem = new DefaultProblem( + VALID_TYPE, + null, + STATUS, + VALID_DETAIL, + VALID_INSTANCE, + CAUSE, + PARAMETERS); + + assertEquals(VALID_TYPE, problem.getType()); + assertNull(problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertEquals(VALID_DETAIL, problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertEquals(CAUSE, problem.getCause()); + assertEquals(PARAMETERS, problem.getParameters()); + } + + @Test + @DisplayName("1.05 Test DefaultProblem with one null status values") + public void DefaultProblemWithStatusNull() { + DefaultProblem problem = new DefaultProblem( + VALID_TYPE, + VALID_TITLE, + null, + VALID_DETAIL, + VALID_INSTANCE, + CAUSE, + PARAMETERS); + + assertEquals(VALID_TYPE, problem.getType()); + assertEquals(VALID_TITLE, problem.getTitle()); + assertNull(problem.getStatus()); + assertEquals(VALID_DETAIL, problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertEquals(CAUSE, problem.getCause()); + assertEquals(PARAMETERS, problem.getParameters()); + } + + @Test + @DisplayName("1.06 Test DefaultProblem with one null detail values") + public void DefaultProblemWithDetailNull() { + DefaultProblem problem = new DefaultProblem( + VALID_TYPE, + VALID_TITLE, + STATUS, + null, + VALID_INSTANCE, + CAUSE, + PARAMETERS); + + assertEquals(VALID_TYPE, problem.getType()); + assertEquals(VALID_TITLE, problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertNull(problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertEquals(CAUSE, problem.getCause()); + assertEquals(PARAMETERS, problem.getParameters()); + } + + @Test + @DisplayName("1.07 Test DefaultProblem with one null instance values") + public void DefaultProblemWithInstanceNull() { + DefaultProblem problem = new DefaultProblem( + VALID_TYPE, + VALID_TITLE, + STATUS, + VALID_DETAIL, + null, + CAUSE, + PARAMETERS); + + assertEquals(VALID_TYPE, problem.getType()); + assertEquals(VALID_TITLE, problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertEquals(VALID_DETAIL, problem.getDetail()); + assertNull(problem.getInstance()); + assertEquals(CAUSE, problem.getCause()); + assertEquals(PARAMETERS, problem.getParameters()); + } + + @Test + @DisplayName("1.08 Test DefaultProblem with one null cause values") + public void DefaultProblemWithCauseNull() { + DefaultProblem problem = new DefaultProblem( + VALID_TYPE, + VALID_TITLE, + STATUS, + VALID_DETAIL, + VALID_INSTANCE, + null, + PARAMETERS); + + assertEquals(VALID_TYPE, problem.getType()); + assertEquals(VALID_TITLE, problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertEquals(VALID_DETAIL, problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertNull(problem.getCause()); + assertEquals(PARAMETERS, problem.getParameters()); + } + + @Test + @DisplayName("1.09 Test DefaultProblem with one null parameters values") + public void DefaultProblemWithParametersNull() { + DefaultProblem problem = new DefaultProblem( + VALID_TYPE, + VALID_TITLE, + STATUS, + VALID_DETAIL, + VALID_INSTANCE, + CAUSE, + null); + + assertEquals(VALID_TYPE, problem.getType()); + assertEquals(VALID_TITLE, problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertEquals(VALID_DETAIL, problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertEquals(CAUSE, problem.getCause()); + assertTrue(problem.getParameters().isEmpty()); + } + + @Test + @DisplayName("1.10.1 Test DefaultProblem with more than one null values") + public void DefaultProblemWithMoreThanOneNull_1() { + DefaultProblem problem = new DefaultProblem( + VALID_TYPE, + null, + STATUS, + null, + VALID_INSTANCE, + CAUSE, + null); + + assertEquals(VALID_TYPE, problem.getType()); + assertNull(problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertNull(problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertEquals(CAUSE, problem.getCause()); + assertTrue(problem.getParameters().isEmpty()); + } + @Test + @DisplayName("1.10.2 Test DefaultProblem with more than one null values") + public void DefaultProblemWithMoreThanOneNull_2() { + DefaultProblem problem = new DefaultProblem( + VALID_TYPE, + null, + STATUS, + null, + null, + CAUSE, + null); + + assertEquals(VALID_TYPE, problem.getType()); + assertNull(problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertNull(problem.getDetail()); + assertNull(problem.getInstance()); + assertEquals(CAUSE, problem.getCause()); + assertTrue(problem.getParameters().isEmpty()); + } + + @Test + @DisplayName("1.10.3 Test DefaultProblem with more than one null values") + public void DefaultProblemWithMoreThanOneNull_3() { + DefaultProblem problem = new DefaultProblem( + null, + null, + STATUS, + null, + VALID_INSTANCE, + null, + null); + + assertEquals(Problem.DEFAULT_TYPE, problem.getType()); + assertNull(problem.getTitle()); + assertEquals(STATUS, problem.getStatus()); + assertNull(problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertNull(problem.getCause()); + assertTrue(problem.getParameters().isEmpty()); + } + + @Test + @DisplayName("1.10.4 Test DefaultProblem with more than one null values") + public void DefaultProblemWithMoreThanOneNull_4() { + DefaultProblem problem = new DefaultProblem( + null, + VALID_TITLE, + null, + VALID_DETAIL, + VALID_INSTANCE, + null, + PARAMETERS); + + assertEquals(Problem.DEFAULT_TYPE, problem.getType()); + assertEquals(VALID_TITLE, problem.getTitle()); + assertNull(problem.getStatus()); + assertEquals(VALID_DETAIL, problem.getDetail()); + assertEquals(VALID_INSTANCE, problem.getInstance()); + assertNull(problem.getCause()); + assertEquals(PARAMETERS, problem.getParameters()); + } + } + + @Nested + class TestCase_2_DefaultProblem_SetParametersFunction { + private DefaultProblem problem; + + @BeforeEach + public void setUp() { + problem = new DefaultProblem(null, null, null, null, null, null, null); + } + + @Test + @DisplayName("2.1 noInput") + public void testGetParametersWithNoInput() { + assertTrue(problem.getParameters().isEmpty()); + } + + @Test + @DisplayName("2.2 input Object is null") + public void testGetParametersWithObjectNull() { + problem.set("null", null); + + assertEquals(1, problem.getParameters().size()); + assertNull(problem.getParameters().get("null")); + } + + @Test + @DisplayName("2.3 input String is null") + public void testGetParametersWithKeyNull() { + problem.set(null, "null"); + + assertEquals(1, problem.getParameters().size()); + for (Map.Entry entry : problem.getParameters().entrySet()) { + if (entry.getValue().equals("null")) { + assertNull(entry.getKey()); + } + } + } + + @Test + @DisplayName("2.4 Both input is null") + public void testGetParametersWithBothNull() { + problem.set(null, null); + assertEquals(1, problem.getParameters().size()); + + Map.Entry firstEntry = null; + for (Map.Entry entry : problem.getParameters().entrySet()) { + firstEntry = entry; + break; + } + assertNull(firstEntry.getKey()); + assertNull(firstEntry.getValue()); + } + + @Test + @DisplayName("2.5 set (add) a parameter") + public void testGetParametersWithDiffTypeMap() { + problem.set("key", "value"); + assertEquals(1, problem.getParameters().size()); + assertEquals("value", problem.getParameters().get("key")); + } + + @Test + @DisplayName("2.6 override an existing parameter") + public void testGetParametersWithOverride() { + problem.set("key", "value1"); + problem.set("key", "value2"); + + assertEquals(1, problem.getParameters().size()); + assertEquals("value2", problem.getParameters().get("key")); + } + + @Test + @DisplayName("2.7 set (add) many different type of Object parameter") + public void testGetParametersWithMultType() { + List array = Arrays.asList("reading", "gaming", "hiking"); + Map someMap = new HashMap<>() {{ + put("name", "Alice"); + put("age", 30); + put("isMember", true); + }}; + LocalDate data = LocalDate.of(2023, 10, 15); + problem.set("stringParam", "value"); + problem.set("intParam", 42); + problem.set("boolParam", true); + problem.set("hobbies", array); + problem.set("customer", someMap); + problem.set("eventDate", data); + + assertEquals(6, problem.getParameters().size()); + assertEquals("value", problem.getParameters().get("stringParam")); + assertEquals(42, problem.getParameters().get("intParam")); + assertEquals(true, problem.getParameters().get("boolParam")); + assertEquals(array, problem.getParameters().get("hobbies")); + assertEquals(someMap, problem.getParameters().get("customer")); + assertEquals(data, problem.getParameters().get("eventDate")); + } + + } + + @Nested + class TestCase_3_DefaultProblem_GetTypeFunction { + @Test + @DisplayName("3.1. input type is null") + public void testGetTypeWithNullType() { + DefaultProblem problem = new DefaultProblem( + null, null, null, null, null, null, null); + + assertEquals(Problem.DEFAULT_TYPE, problem.getType()); + } + + @Test + @DisplayName("3.2. input type is a standard URI") + public void testGetTypeWithStandardUri() { + URI standardUri = URI.create("https://example.org/problems/test"); + DefaultProblem problem = new DefaultProblem( + standardUri, null, null, null, null, null, null); + + assertEquals(standardUri, problem.getType()); + } + + @Test + @DisplayName("3.3. input type is a custom scheme URI") + public void testGetTypeWithCustomSchemeUri() { + URI customUri = URI.create("problem:out-of-stock"); + DefaultProblem problem = new DefaultProblem( + customUri, null, null, null, null, null, null); + + assertEquals(customUri, problem.getType()); + } + + @Test + @DisplayName("3.4. input type is a URI with query parameters") + public void testGetTypeWithUriWithQueryParameters() { + URI uriWithQuery = URI.create("https://example.org/problems/test?param=value"); + DefaultProblem problem = new DefaultProblem( + uriWithQuery, null, null, null, null, null, null); + + assertEquals(uriWithQuery, problem.getType()); + } + + @Test + @DisplayName("3.5. input type is a URI with fragment") + public void testGetTypeWithUriWithFragment() { + URI uriWithFragment = URI.create("https://example.org/problems/test#section"); + DefaultProblem problem = new DefaultProblem( + uriWithFragment, null, null, null, null, null, null); + + assertEquals(uriWithFragment, problem.getType()); + } + } + + @Nested + class TestCase_4_DefaultProblem_GetTitleFunction { + @Test + @DisplayName("4.1 title is null") + public void testGetTitleWithNullTitle() { + DefaultProblem problem = new DefaultProblem( + null, null, null, null, null, null, null); + + assertNull(problem.getTitle()); + } + + @Test + @DisplayName("4.2 non-empty title") + public void testGetTitleWithNonEmptyTitle() { + String title = "Test Problem"; + DefaultProblem problem = new DefaultProblem( + null, title, null, null, null, null, null); + + assertEquals(title, problem.getTitle()); + } + + @Test + @DisplayName("4.3 when title is empty") + public void testGetTitleWithEmptyTitle() { + DefaultProblem problem = new DefaultProblem( + null, "", null, null, null, null, null); + + assertEquals("", problem.getTitle()); + } + + @Test + @DisplayName("4.4 title with special characters") + public void testGetTitleWithSpecialCharacters() { + String titleWithSpecialChars = "Problem: 特殊字符 & 😀"; + DefaultProblem problem = new DefaultProblem( + null, titleWithSpecialChars, null, null, null, null, null); + + assertEquals(titleWithSpecialChars, problem.getTitle()); + } + + @Test + @DisplayName("4.5 very long title") + public void testGetTitleWithVeryLongTitle() { + String longTitle = "This is a very long title that contains more than a hundred characters to test how the getTitle method handles long string values."; + DefaultProblem problem = new DefaultProblem( + null, longTitle, null, null, null, null, null); + + assertEquals(longTitle, problem.getTitle()); + } + } + + @Nested + class TestCase_5_DefaultProblem_GetNameFunction { + private static class TestStatus implements StatusType { + private final int code; + private final String reason; + + public TestStatus(int code, String reason) { + this.code = code; + this.reason = reason; + } + + @Override + public int getStatusCode() { + return code; + } + + @Override + public String getReasonPhrase() { + return reason; + } + } + + @Test + @DisplayName("5.1. status is null") + public void testGetStatusWithNullStatus() { + DefaultProblem problem = new DefaultProblem( + null, null, null, null, null, null, null); + + assertNull(problem.getStatus()); + } + + @Test + @DisplayName("5.2. standard HTTP status") + public void testGetStatusWithStandardHttpStatus() { + StatusType status = new TestStatus(404, "Not Found"); + DefaultProblem problem = new DefaultProblem( + null, null, status, null, null, null, null); + + assertEquals(status, problem.getStatus()); + assertEquals(404, problem.getStatus().getStatusCode()); + assertEquals("Not Found", problem.getStatus().getReasonPhrase()); + } + + @Test + @DisplayName("5.3. custom status") + public void testGetStatusWithCustomStatus() { + StatusType status = new TestStatus(418, "I'm a teapot"); + DefaultProblem problem = new DefaultProblem( + null, null, status, null, null, null, null); + + assertEquals(status, problem.getStatus()); + assertEquals(418, problem.getStatus().getStatusCode()); + assertEquals("I'm a teapot", problem.getStatus().getReasonPhrase()); + } + + @Test + @DisplayName("5.4. error status") + public void testGetStatusWithErrorStatus() { + StatusType status = new TestStatus(500, "Internal Server Error"); + DefaultProblem problem = new DefaultProblem( + null, null, status, null, null, null, null); + + assertEquals(status, problem.getStatus()); + assertEquals(500, problem.getStatus().getStatusCode()); + assertEquals("Internal Server Error", problem.getStatus().getReasonPhrase()); + } + + @Test + @DisplayName("5.5. redirect status") + public void testGetStatusWithRedirectStatus() { + StatusType status = new TestStatus(302, "Found"); + DefaultProblem problem = new DefaultProblem( + null, null, status, null, null, null, null); + + assertEquals(status, problem.getStatus()); + assertEquals(302, problem.getStatus().getStatusCode()); + assertEquals("Found", problem.getStatus().getReasonPhrase()); + } + } + + @Nested + class TestCase_6_DefaultProblem_GetDetailFunction { + @Test + @DisplayName("6.1. detail is null") + public void testGetDetailWithNullDetail() { + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, null, null, null, null); + + assertNull(problem.getDetail()); + } + + @Test + @DisplayName("6.2. non-empty detail") + public void testGetDetailWithNonEmptyDetail() { + String detail = "This is a test problem detail"; + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, detail, null, null, null); + + assertEquals(detail, problem.getDetail()); + } + + @Test + @DisplayName("6.3. detail is empty") + public void testGetDetailWithEmptyDetail() { + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, "", null, null, null); + + assertEquals("", problem.getDetail()); + } + + @Test + @DisplayName("6.4. detail with special characters") + public void testGetDetailWithSpecialCharacters() { + String detailWithSpecialChars = "Detail with \n newlines \t tabs and other \"special\" 'chars'"; + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, detailWithSpecialChars, null, null, null); + + assertEquals(detailWithSpecialChars, problem.getDetail()); + } + + @Test + @DisplayName("6.5. very long detail") + public void testGetDetailWithVeryLongDetail() { + StringBuilder longDetailBuilder = new StringBuilder(); + for (int i = 0; i < 10; i++) { + longDetailBuilder.append("This is a very long detail message. "); + } + String longDetail = longDetailBuilder.toString(); + + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, longDetail, null, null, null); + + assertEquals(longDetail, problem.getDetail()); + } + } + + @Nested + class TestCase_7_DefaultProblem_GetParametersFunction { + @Test + @DisplayName("7.1. parameters is null") + public void testGetParametersWithNullParameters() { + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, null, null, null, null); + + assertNotNull(problem.getParameters()); + assertTrue(problem.getParameters().isEmpty()); + } + + @Test + @DisplayName("7.2. empty parameters") + public void testGetParametersWithEmptyParameters() { + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, null, null, null, new HashMap<>()); + + assertNotNull(problem.getParameters()); + assertTrue(problem.getParameters().isEmpty()); + } + + @Test + @DisplayName("7.3. parameters with string values") + public void testGetParametersWithStringValues() { + Map parameters = new HashMap<>(); + parameters.put("param1", "value1"); + parameters.put("param2", "value2"); + + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, null, null, null, parameters); + + assertEquals(2, problem.getParameters().size()); + assertEquals("value1", problem.getParameters().get("param1")); + assertEquals("value2", problem.getParameters().get("param2")); + } + + @Test + @DisplayName("7.4. parameters with various value types") + public void testGetParametersWithVariousValueTypes() { + Map parameters = new HashMap<>(); + parameters.put("stringParam", "value"); + parameters.put("intParam", 42); + parameters.put("boolParam", true); + parameters.put("nullParam", null); + + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, null, null, null, parameters); + + assertEquals(4, problem.getParameters().size()); + assertEquals("value", problem.getParameters().get("stringParam")); + assertEquals(42, problem.getParameters().get("intParam")); + assertEquals(true, problem.getParameters().get("boolParam")); + assertNull(problem.getParameters().get("nullParam")); + } + + @Test + @DisplayName("7.5. parameters with nested map") + public void testGetParametersWithNestedMap() { + Map nestedMap = new HashMap<>(); + nestedMap.put("nestedKey", "nestedValue"); + + Map parameters = new HashMap<>(); + parameters.put("param", "value"); + parameters.put("nestedMap", nestedMap); + + AbstractThrowableProblem problem = new DefaultProblem( + null, null, null, null, null, null, parameters); + + assertEquals(2, problem.getParameters().size()); + assertEquals("value", problem.getParameters().get("param")); + + Map returnedNestedMap = (Map) problem.getParameters().get("nestedMap"); + assertEquals("nestedValue", returnedNestedMap.get("nestedKey")); + } + } + + @Nested + class TestCase_8_DefaultProblem_GetCauseFunction { + @Test + @DisplayName("8.1. cause is null") + public void testGetCauseWithNullCause() { + DefaultProblem problem = new DefaultProblem( + null, null, null, null, null, null, null); + + assertNull(problem.getCause()); + } + + @Test + @DisplayName("8.2. empty cause") + public void testGetCauseWithEmptyCause() { + DefaultProblem cause = new DefaultProblem( + null, + null, + null, + null, + null, + null, + null); + + DefaultProblem problem = new DefaultProblem( + null, null, null, null, null, cause, null); + + assertNotNull(problem.getCause()); + assertEquals(cause, problem.getCause()); + } + + @Test + @DisplayName("8.3. non empty cause") + public void testGetCauseWithProvidedCause() { + DefaultProblem cause = new DefaultProblem( + URI.create("https://example.org/problems/cause"), + "Cause Problem", + null, + "This is the cause", + null, + null, + null); + + DefaultProblem problem = new DefaultProblem( + null, null, null, null, null, cause, null); + + assertEquals(cause, problem.getCause()); + } + + @Test + @DisplayName("8.4. cause with nested cause") + public void testGetCauseWithNestedCause() { + DefaultProblem nestedCause = new DefaultProblem( + URI.create("https://example.org/problems/nested-cause"), + "Nested Cause Problem", + null, + "This is the nested cause", + null, + null, + null); + + DefaultProblem cause = new DefaultProblem( + URI.create("https://example.org/problems/cause"), + "Cause Problem", + null, + "This is the cause", + null, + nestedCause, + null); + + DefaultProblem problem = new DefaultProblem( + null, null, null, null, null, cause, null); + + assertEquals(cause, problem.getCause()); + assertEquals(nestedCause, ((DefaultProblem) problem.getCause()).getCause()); + } + } + + @Nested + class TestCase_9_DefaultProblem_getMessageFunction { + private static final String VALID_TITLE = "Test Problem"; + private static final String VALID_DETAIL = "This is a test problem detail"; + + @Test + @DisplayName("9.1. both title and detail") + public void testGetMessageWithTitleAndDetail() { + DefaultProblem problem = new DefaultProblem( + null, VALID_TITLE, null, VALID_DETAIL, null, null); + + assertEquals(VALID_TITLE + ": " + VALID_DETAIL, problem.getMessage()); + } + + @Test + @DisplayName("9.2. only title") + public void testGetMessageWithTitleOnly() { + DefaultProblem problem = new DefaultProblem( + null, VALID_TITLE, null, null, null, null); + + assertEquals(VALID_TITLE, problem.getMessage()); + } + + @Test + @DisplayName("9.3. only detail") + public void testGetMessageWithDetailOnly() { + DefaultProblem problem = new DefaultProblem( + null, null, null, VALID_DETAIL, null, null); + + assertEquals(VALID_DETAIL, problem.getMessage()); + } + + @Test + @DisplayName("9.4. title and detail are null") + public void testGetMessageWithNullTitleAndDetail() { + DefaultProblem problem = new DefaultProblem( + null, null, null, null, null, null); + + assertEquals("", problem.getMessage()); + } + + @Test + @DisplayName("9.5. empty title and detail") + public void testGetMessageWithEmptyTitleAndDetail() { + DefaultProblem problem = new DefaultProblem( + null, "", null, "", null, null); + + assertEquals(": ", problem.getMessage()); + } + } + + @Nested + class TestCase_10_Status_ValueOfFunction { + @Test + @DisplayName("10.1. correct status codes") + public void testValueOfCorrectInput() { + assertEquals(Status.CONTINUE, Status.valueOf(100)); + assertEquals(Status.SWITCHING_PROTOCOLS, Status.valueOf(101)); + assertEquals(Status.PROCESSING, Status.valueOf(102)); + assertEquals(Status.CHECKPOINT, Status.valueOf(103)); + assertEquals(Status.OK, Status.valueOf(200)); + assertEquals(Status.CREATED, Status.valueOf(201)); + assertEquals(Status.ACCEPTED, Status.valueOf(202)); + assertEquals(Status.NON_AUTHORITATIVE_INFORMATION, Status.valueOf(203)); + assertEquals(Status.NO_CONTENT, Status.valueOf(204)); + assertEquals(Status.RESET_CONTENT, Status.valueOf(205)); + assertEquals(Status.PARTIAL_CONTENT, Status.valueOf(206)); + assertEquals(Status.MULTI_STATUS, Status.valueOf(207)); + assertEquals(Status.ALREADY_REPORTED, Status.valueOf(208)); + assertEquals(Status.IM_USED, Status.valueOf(226)); + assertEquals(Status.MULTIPLE_CHOICES, Status.valueOf(300)); + assertEquals(Status.MOVED_PERMANENTLY, Status.valueOf(301)); + assertEquals(Status.FOUND, Status.valueOf(302)); + assertEquals(Status.SEE_OTHER, Status.valueOf(303)); + assertEquals(Status.NOT_MODIFIED, Status.valueOf(304)); + assertEquals(Status.USE_PROXY, Status.valueOf(305)); + assertEquals(Status.TEMPORARY_REDIRECT, Status.valueOf(307)); + assertEquals(Status.PERMANENT_REDIRECT, Status.valueOf(308)); + assertEquals(Status.BAD_REQUEST, Status.valueOf(400)); + assertEquals(Status.UNAUTHORIZED, Status.valueOf(401)); + assertEquals(Status.PAYMENT_REQUIRED, Status.valueOf(402)); + assertEquals(Status.FORBIDDEN, Status.valueOf(403)); + assertEquals(Status.NOT_FOUND, Status.valueOf(404)); + assertEquals(Status.METHOD_NOT_ALLOWED, Status.valueOf(405)); + assertEquals(Status.NOT_ACCEPTABLE, Status.valueOf(406)); + assertEquals(Status.PROXY_AUTHENTICATION_REQUIRED, Status.valueOf(407)); + assertEquals(Status.REQUEST_TIMEOUT, Status.valueOf(408)); + assertEquals(Status.CONFLICT, Status.valueOf(409)); + assertEquals(Status.GONE, Status.valueOf(410)); + assertEquals(Status.LENGTH_REQUIRED, Status.valueOf(411)); + assertEquals(Status.PRECONDITION_FAILED, Status.valueOf(412)); + assertEquals(Status.REQUEST_ENTITY_TOO_LARGE, Status.valueOf(413)); + assertEquals(Status.REQUEST_URI_TOO_LONG, Status.valueOf(414)); + assertEquals(Status.UNSUPPORTED_MEDIA_TYPE, Status.valueOf(415)); + assertEquals(Status.REQUESTED_RANGE_NOT_SATISFIABLE, Status.valueOf(416)); + assertEquals(Status.EXPECTATION_FAILED, Status.valueOf(417)); + assertEquals(Status.I_AM_A_TEAPOT, Status.valueOf(418)); + assertEquals(Status.UNPROCESSABLE_ENTITY, Status.valueOf(422)); + assertEquals(Status.LOCKED, Status.valueOf(423)); + assertEquals(Status.FAILED_DEPENDENCY, Status.valueOf(424)); + assertEquals(Status.UPGRADE_REQUIRED, Status.valueOf(426)); + assertEquals(Status.PRECONDITION_REQUIRED, Status.valueOf(428)); + assertEquals(Status.TOO_MANY_REQUESTS, Status.valueOf(429)); + assertEquals(Status.REQUEST_HEADER_FIELDS_TOO_LARGE, Status.valueOf(431)); + assertEquals(Status.UNAVAILABLE_FOR_LEGAL_REASONS, Status.valueOf(451)); + assertEquals(Status.INTERNAL_SERVER_ERROR, Status.valueOf(500)); + assertEquals(Status.NOT_IMPLEMENTED, Status.valueOf(501)); + assertEquals(Status.BAD_GATEWAY, Status.valueOf(502)); + assertEquals(Status.SERVICE_UNAVAILABLE, Status.valueOf(503)); + assertEquals(Status.GATEWAY_TIMEOUT, Status.valueOf(504)); + assertEquals(Status.HTTP_VERSION_NOT_SUPPORTED, Status.valueOf(505)); + assertEquals(Status.VARIANT_ALSO_NEGOTIATES, Status.valueOf(506)); + assertEquals(Status.INSUFFICIENT_STORAGE, Status.valueOf(507)); + assertEquals(Status.LOOP_DETECTED, Status.valueOf(508)); + assertEquals(Status.BANDWIDTH_LIMIT_EXCEEDED, Status.valueOf(509)); + assertEquals(Status.NOT_EXTENDED, Status.valueOf(510)); + assertEquals(Status.NETWORK_AUTHENTICATION_REQUIRED, Status.valueOf(511)); + } + + @ParameterizedTest + @ValueSource(ints = {-1, 0, 600, 999}) + @DisplayName("10.2. unknown status codes") + public void testValueOfThrowsExceptionForUnknownCodes(int unknownCode) { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> Status.valueOf(unknownCode) + ); + + assertTrue(exception.getMessage().contains(Integer.toString(unknownCode))); + assertTrue(exception.getMessage().contains("There is no known status")); + } + } + + +}