diff --git a/src/test/java/hello/ApplicationContextLoadTest.java b/src/test/java/hello/ApplicationContextLoadTest.java new file mode 100644 index 0000000..e92dbb7 --- /dev/null +++ b/src/test/java/hello/ApplicationContextLoadTest.java @@ -0,0 +1,49 @@ +package hello; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.client.RestTemplate; + +import hello.controller.GreetingController; +import hello.controller.HelloController; +import hello.controller.TopicController; +import hello.service.TopicService; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Starts the whole application context, including the {@link org.springframework.boot.CommandLineRunner} + * on {@link Application}. The runner contacts a demo quote service that no longer exists; the resulting + * WARN must not fail startup, so reaching the assertions below is itself part of what is being verified. + */ +@SpringBootTest +class ApplicationContextLoadTest { + + @Autowired + private ApplicationContext context; + + @Test + void contextLoads() { + assertThat(context).isNotNull(); + } + + @Test + void exposesTheExpectedBeans() { + assertThat(context.getBean(TopicService.class)).isNotNull(); + assertThat(context.getBean(TopicController.class)).isNotNull(); + assertThat(context.getBean(HelloController.class)).isNotNull(); + assertThat(context.getBean(GreetingController.class)).isNotNull(); + assertThat(context.getBean(RestTemplate.class)).isNotNull(); + assertThat(context.getBean(JdbcTemplate.class)).isNotNull(); + } + + @Test + void commandLineRunnerPopulatedTheCustomersTable() { + Integer count = context.getBean(JdbcTemplate.class) + .queryForObject("SELECT COUNT(*) FROM customers", Integer.class); + assertThat(count).isEqualTo(4); + } +} diff --git a/src/test/java/hello/controller/GreetingControllerWebTest.java b/src/test/java/hello/controller/GreetingControllerWebTest.java new file mode 100644 index 0000000..ad8fd25 --- /dev/null +++ b/src/test/java/hello/controller/GreetingControllerWebTest.java @@ -0,0 +1,61 @@ +package hello.controller; + +import com.jayway.jsonpath.JsonPath; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Pins the JSON contract of {@code /}: {@code {"id": , "content": }}. + */ +@SpringBootTest +@AutoConfigureMockMvc +class GreetingControllerWebTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void greetsTheWorldByDefault() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.*", hasSize(2))) + .andExpect(jsonPath("$.id").isNumber()) + .andExpect(jsonPath("$.content").value("Hello, World!")); + } + + @Test + void greetsTheRequestedName() throws Exception { + mockMvc.perform(get("/").param("name", "Devin")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content").value("Hello, Devin!")); + } + + @Test + void everyGreetingGetsItsOwnId() throws Exception { + long first = greetingId(); + long second = greetingId(); + + assertThat(second).isEqualTo(first + 1); + } + + private long greetingId() throws Exception { + String json = mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + return JsonPath.parse(json).read("$.id", Long.class); + } +} diff --git a/src/test/java/hello/controller/HelloControllerWebTest.java b/src/test/java/hello/controller/HelloControllerWebTest.java new file mode 100644 index 0000000..e99f741 --- /dev/null +++ b/src/test/java/hello/controller/HelloControllerWebTest.java @@ -0,0 +1,63 @@ +package hello.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.matchesRegex; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Covers the plain-text endpoints. {@code /topic/file/operation} walks the process working directory + * and reads {@code temp.txt} from it, so its exact output changes whenever a file is added to or + * removed from the repository; only the stable parts of the response are asserted. + */ +@SpringBootTest +@AutoConfigureMockMvc +class HelloControllerWebTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void datetimeEndpointRendersTheJavaTimeShowcase() throws Exception { + mockMvc.perform(get("/datetime")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("Greetings from Spring Boot!"))) + .andExpect(content().string(containsString("Datetime now is "))) + .andExpect(content().string(containsString("Is this a leap year ?"))) + .andExpect(content().string(containsString("Default system zone id"))) + .andExpect(content().string(containsString("Time in California: "))) + // "Datetime now is " + .andExpect(content().string(matchesRegex("(?s).*Datetime now is \\d{4}-\\d{2}-\\d{2}T[\\d:.]+.*"))); + } + + @Test + void stringOperationEndpointRendersTheStreamShowcase() throws Exception { + mockMvc.perform(get("/topic/string/operation")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("Joining All String ID's with JOIN method: "))) + // The distinct-and-sorted characters and the ":"-split are both sorted, so they are + // stable even if another test has re-ordered the singleton's topic list. + .andExpect(content().string(containsString(":acgijnprstv"))) + .andExpect(content().string(containsString("java:javascript"))) + .andExpect(content().string(containsString("[spring]"))); + } + + @Test + void fileOperationEndpointRendersTheNioShowcase() throws Exception { + mockMvc.perform(get("/topic/file/operation")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("Find all files in path and sort:"))) + .andExpect(content().string(containsString("pom.xml"))) + .andExpect(content().string(containsString("Read \"temp.txt\" file with stream functions"))) + .andExpect(content().string(not(containsString("Error in IO")))) + .andExpect(content().string(not(containsString("IO exception")))); + } +} diff --git a/src/test/java/hello/controller/TopicControllerWebTest.java b/src/test/java/hello/controller/TopicControllerWebTest.java new file mode 100644 index 0000000..05147be --- /dev/null +++ b/src/test/java/hello/controller/TopicControllerWebTest.java @@ -0,0 +1,125 @@ +package hello.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * End-to-end coverage of the JSON contract of the topic endpoints. + * + *

{@code TopicService} is a singleton holding a mutable list, so anything that writes to it leaks + * into the next test. Rather than depending on test order, the two tests that mutate the list + * ({@code /topic} writes and the in-place sort performed by {@code /topic/sort}) drop the context + * afterwards with {@link DirtiesContext}; every other test can then assume the seeded state. + * + *

Assertions go through {@code jsonPath} rather than through model getters on purpose: the JSON + * field names are the actual contract and they survive the ongoing conversion of the models to records. + */ +@SpringBootTest +@AutoConfigureMockMvc +class TopicControllerWebTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void getAllTopicsReturnsTheSeededTopics() throws Exception { + mockMvc.perform(get("/topic")) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$", hasSize(3))) + .andExpect(jsonPath("$[*].id", containsInAnyOrder("spring", "java", "javascript"))) + .andExpect(jsonPath("$[?(@.id=='spring')].subjectName", contains("Spring Framework"))) + .andExpect(jsonPath("$[?(@.id=='spring')].subjectDescription", contains("Spring Framework Description"))); + } + + @Test + void aTopicSerialisesWithExactlyIdSubjectNameAndSubjectDescription() throws Exception { + mockMvc.perform(get("/topic/java")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.*", hasSize(3))) + .andExpect(jsonPath("$.id").value("java")) + .andExpect(jsonPath("$.subjectName").value("Core Java")) + .andExpect(jsonPath("$.subjectDescription").value("Java Description")); + } + + @Test + @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) + void supportsTheFullCreateReadUpdateDeleteLifecycle() throws Exception { + mockMvc.perform(post("/topic") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + {"id":"kotlin","subjectName":"Kotlin","subjectDescription":"Kotlin Description"}""")) + .andExpect(status().isOk()); + + mockMvc.perform(get("/topic")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(4))) + .andExpect(jsonPath("$[*].id", containsInAnyOrder("spring", "java", "javascript", "kotlin"))); + + mockMvc.perform(get("/topic/kotlin")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subjectName").value("Kotlin")) + .andExpect(jsonPath("$.subjectDescription").value("Kotlin Description")); + + mockMvc.perform(put("/topic/kotlin") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + {"id":"kotlin","subjectName":"Kotlin Updated","subjectDescription":"Updated Description"}""")) + .andExpect(status().isOk()); + + mockMvc.perform(get("/topic/kotlin")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subjectName").value("Kotlin Updated")) + .andExpect(jsonPath("$.subjectDescription").value("Updated Description")); + + mockMvc.perform(delete("/topic/kotlin")) + .andExpect(status().isOk()); + + mockMvc.perform(get("/topic")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(3))) + .andExpect(jsonPath("$[*].id", not(hasItem("kotlin")))); + } + + @Test + @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) + void sortEndpointReturnsTopicsOrderedById() throws Exception { + mockMvc.perform(get("/topic/sort")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(3))) + .andExpect(jsonPath("$[*].id", contains("java", "javascript", "spring"))); + } + + @Test + void minimumLengthEndpointFiltersOnIdLength() throws Exception { + mockMvc.perform(get("/topic/minimum/length/4")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[*].id", containsInAnyOrder("spring", "javascript"))); + + mockMvc.perform(get("/topic/minimum/length/6")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[*].id", contains("javascript"))); + + mockMvc.perform(get("/topic/minimum/length/20")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(0))); + } +} diff --git a/src/test/java/hello/controller/TopicNotFoundStatusTest.java b/src/test/java/hello/controller/TopicNotFoundStatusTest.java new file mode 100644 index 0000000..398aed6 --- /dev/null +++ b/src/test/java/hello/controller/TopicNotFoundStatusTest.java @@ -0,0 +1,54 @@ +package hello.controller; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Requesting an unknown topic id is the one place where the HTTP status carries real information, and + * MockMvc rethrows unhandled exceptions instead of running the error dispatch, so this one case is + * driven over a real connection. + * + *

{@code TopicService.getTopicWithId} calls {@code Optional.get()} on a miss, which throws + * {@link java.util.NoSuchElementException} and surfaces as HTTP 500. That is asserted here as the + * current behaviour; the disabled test below is the replacement for when a parallel change + * turns the miss into a proper 404. + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class TopicNotFoundStatusTest { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void unknownTopicIdCurrentlyReturnsInternalServerError() { + ResponseEntity response = restTemplate.getForEntity("/topic/does-not-exist", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + } + + @Test + @Disabled("Enable, and delete unknownTopicIdCurrentlyReturnsInternalServerError, once an unknown id is a 404") + void unknownTopicIdShouldReturnNotFound() { + ResponseEntity response = restTemplate.getForEntity("/topic/does-not-exist", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + void knownTopicIdReturnsOkWithTheUnchangedJsonShape() { + ResponseEntity response = restTemplate.getForEntity("/topic/spring", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()) + .contains("\"id\":\"spring\"") + .contains("\"subjectName\":\"Spring Framework\"") + .contains("\"subjectDescription\":\"Spring Framework Description\""); + } +} diff --git a/src/test/java/hello/declaration/TimeClientTest.java b/src/test/java/hello/declaration/TimeClientTest.java new file mode 100644 index 0000000..3778f26 --- /dev/null +++ b/src/test/java/hello/declaration/TimeClientTest.java @@ -0,0 +1,80 @@ +package hello.declaration; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.junit.jupiter.api.Test; + +import hello.model.SimpleTimeClient; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Covers the {@code static} and {@code default} methods on the interface and the + * {@link SimpleTimeClient} implementation of them. + */ +class TimeClientTest { + + @Test + void getZoneIdResolvesAKnownZone() { + assertThat(TimeClient.getZoneId("Canada/Central")).isEqualTo(ZoneId.of("Canada/Central")); + } + + @Test + void getZoneIdFallsBackToTheSystemZoneForAnInvalidZone() { + assertThat(TimeClient.getZoneId("Not/A/Zone")).isEqualTo(ZoneId.systemDefault()); + assertThat(TimeClient.getZoneId("")).isEqualTo(ZoneId.systemDefault()); + } + + @Test + void getZonedDateTimeCombinesTheLocalDateTimeWithTheRequestedZone() { + SimpleTimeClient client = new SimpleTimeClient(); + client.setDateAndTime(2020, 5, 17, 13, 45, 30); + + ZonedDateTime zoned = client.getZonedDateTime("Canada/Central"); + + assertThat(zoned.toLocalDateTime()).isEqualTo(LocalDateTime.of(2020, 5, 17, 13, 45, 30)); + assertThat(zoned.getZone()).isEqualTo(ZoneId.of("Canada/Central")); + } + + @Test + void getZonedDateTimeUsesTheSystemZoneWhenTheZoneIsInvalid() { + SimpleTimeClient client = new SimpleTimeClient(); + + assertThat(client.getZonedDateTime("Middle/Earth").getZone()).isEqualTo(ZoneId.systemDefault()); + } + + @Test + void newClientIsInitialisedWithTheCurrentDateTime() { + LocalDateTime before = LocalDateTime.now().minusMinutes(1); + + SimpleTimeClient client = new SimpleTimeClient(); + + assertThat(client.getLocalDateTime()).isAfter(before); + assertThat(client.toString()).isEqualTo(client.getLocalDateTime().toString()); + } + + @Test + void setTimeKeepsTheDateAndReplacesTheTime() { + SimpleTimeClient client = new SimpleTimeClient(); + client.setDateAndTime(2020, 5, 17, 1, 2, 3); + + client.setTime(23, 59, 58); + + assertThat(client.getLocalDateTime()).isEqualTo(LocalDateTime.of(2020, 5, 17, 23, 59, 58)); + } + + @Test + void setDateKeepsTheTimeAndReplacesTheDate() { + SimpleTimeClient client = new SimpleTimeClient(); + client.setDateAndTime(2020, 5, 17, 8, 30, 0); + + // setDate/setDateAndTime declare their parameters as (day, month, year) but delegate to + // LocalDate.of(..) positionally, so the first argument is really the year. Asserted as + // implemented rather than as named. + client.setDate(2021, 12, 25); + + assertThat(client.getLocalDateTime()).isEqualTo(LocalDateTime.of(2021, 12, 25, 8, 30, 0)); + } +} diff --git a/src/test/java/hello/service/TopicServiceTest.java b/src/test/java/hello/service/TopicServiceTest.java new file mode 100644 index 0000000..ed97e35 --- /dev/null +++ b/src/test/java/hello/service/TopicServiceTest.java @@ -0,0 +1,191 @@ +package hello.service; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import hello.model.Topic; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * {@link TopicService} keeps its topics in a mutable instance field, so as a Spring singleton its + * state leaks between tests. These unit tests therefore build a brand new service in {@link BeforeEach} + * instead of injecting the bean: every test starts from the three seeded topics and nothing has to be + * undone afterwards. + */ +class TopicServiceTest { + + private TopicService topicService; + + @BeforeEach + void createFreshService() { + topicService = new TopicService(); + } + + /** + * Single place where a {@link Topic} accessor is used, so the parallel conversion of the models to + * records only has to touch one line in this class. + */ + private static List idsOf(List topics) { + return topics.stream().map(Topic::getId).collect(Collectors.toList()); + } + + @Test + void seedsThreeTopics() { + assertThat(idsOf(topicService.getAllTopics())) + .containsExactly("spring", "java", "javascript"); + } + + @Test + void getTopicWithIdReturnsTheMatchingTopic() { + Topic topic = topicService.getTopicWithId("java"); + + assertThat(idsOf(List.of(topic))).containsExactly("java"); + assertThat(topic.getSubjectName()).isEqualTo("Core Java"); + } + + @Test + void getTopicWithIdThrowsForAnUnknownId() { + // Current behaviour: Optional.get() on an empty Optional. Over HTTP this surfaces as a 500. + assertThatThrownBy(() -> topicService.getTopicWithId("does-not-exist")) + .isInstanceOf(NoSuchElementException.class); + } + + @Test + @Disabled("Enable together with the parallel change that makes an unknown id a proper 404") + void getTopicWithIdShouldReportAMissingTopicWithoutThrowing() { + // Replacement for getTopicWithIdThrowsForAnUnknownId once the service returns an empty + // Optional (or the controller translates the miss into HTTP 404). + assertThatCode(() -> topicService.getTopicWithId("does-not-exist")) + .doesNotThrowAnyException(); + } + + @Test + void addTopicAppendsToTheList() { + topicService.addTopic(new Topic("kotlin", "Kotlin", "Kotlin Description")); + + assertThat(idsOf(topicService.getAllTopics())).containsExactly("spring", "java", "javascript", "kotlin"); + } + + @Test + void updateTopicReplacesTheTopicInPlace() { + topicService.updateTopic("java", new Topic("java", "Updated Java", "Updated Description")); + + List topics = topicService.getAllTopics(); + assertThat(idsOf(topics)).containsExactly("spring", "java", "javascript"); + assertThat(topics.get(1).getSubjectName()).isEqualTo("Updated Java"); + } + + @Test + void updateTopicIsANoOpForAnUnknownId() { + topicService.updateTopic("kotlin", new Topic("kotlin", "Kotlin", "Kotlin Description")); + + assertThat(idsOf(topicService.getAllTopics())).containsExactly("spring", "java", "javascript"); + } + + @Test + void deleteTopicRemovesOnlyTheMatchingTopic() { + topicService.deleteTopic("java"); + + assertThat(idsOf(topicService.getAllTopics())).containsExactly("spring", "javascript"); + } + + @Test + void deleteTopicIsANoOpForAnUnknownId() { + topicService.deleteTopic("kotlin"); + + assertThat(idsOf(topicService.getAllTopics())).hasSize(3); + } + + @Test + void filterMinimumLengthForIdKeepsIdsStrictlyLongerThanTheGivenLength() { + assertThat(idsOf(topicService.filterMinimumLengthForId(4))) + .containsExactly("spring", "javascript"); + assertThat(idsOf(topicService.filterMinimumLengthForId(6))) + .containsExactly("javascript"); + assertThat(topicService.filterMinimumLengthForId(20)).isEmpty(); + } + + @Test + void sortTopicsWithIDSortsTheUnderlyingListById() { + assertThat(idsOf(topicService.sortTopicsWithID())) + .containsExactly("java", "javascript", "spring"); + + // The sort mutates the service's own list, which is exactly why these tests use a fresh instance. + assertThat(idsOf(topicService.getAllTopics())) + .containsExactly("java", "javascript", "spring"); + } + + @Test + void returnAllTopicIDWithStringSlicingJoinsIdsWithAColon() { + assertThat(topicService.returnAllTopicIDWithStringSlicing()).isEqualTo("spring:java:javascript"); + } + + @Test + void makeDistinctAndSortCharactersReturnsSortedDistinctCharacters() { + assertThat(topicService.makeDistinctAndSortCharacters("spring:java:javascript")) + .isEqualTo(":acgijnprstv"); + assertThat(topicService.makeDistinctAndSortCharacters("")).isEmpty(); + } + + @Test + void splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoinKeepsOnlyJavaIds() { + assertThat(topicService.splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoin("spring:java:javascript")) + .isEqualTo("java:javascript"); + assertThat(topicService.splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoin("spring:kotlin")) + .isEmpty(); + } + + @Test + void findIdHavingCharacterMatchesIdsContainingG() { + assertThat(topicService.findIdHavingCharacter()).isEqualTo("[spring]"); + } + + // The file operations below walk the process working directory (the module base directory when run + // by Surefire) and read temp.txt from it. Their exact output changes whenever a file is added or + // removed from the repository, so they are asserted loosely on purpose. + + @Test + void findAllFilesInPathAndSortListsVisibleFilesInTheWorkingDirectory() { + String listed = topicService.findAllFilesInPathAndSort(); + + assertThat(listed).isNotNull().isNotEqualTo(" Error in IO"); + assertThat(listed).contains("pom.xml"); + assertThat(listed).doesNotContain(".git;"); + } + + @Test + void findParticularFileInPathAndSortReturnsAStringWithoutFailing() { + assertThat(topicService.findParticularFileInPathAndSort()) + .isNotNull() + .isNotEqualTo(" IO exception "); + } + + @Test + void findParticularFileInPathAndSortWithWalkFunctionReturnsAStringWithoutFailing() { + assertThat(topicService.findParticularFileInPathAndSortWithWalkFunction()) + .isNotNull() + .isNotEqualTo(" IO exception "); + } + + @Test + void readFileWithStreamFunctionReturnsTheLinesStartingWithPrint() { + Assumptions.assumeTrue(Files.exists(Paths.get("temp.txt")), "temp.txt is read relative to the working directory"); + + String read = topicService.readFileWithStreamFunction(); + + assertThat(read).isNotNull().isNotEqualTo(" IO exception "); + assertThat(read).contains(" Hello"); + assertThat(read).doesNotContain("print"); + } +}