diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..71415d7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + branches: [ main, 'devin/**' ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: '11' + distribution: 'temurin' + cache: maven + + - name: Build and verify with Maven + run: mvn -B clean verify diff --git a/MIGRATION_NOTES.md b/MIGRATION_NOTES.md new file mode 100644 index 0000000..47e2ae5 --- /dev/null +++ b/MIGRATION_NOTES.md @@ -0,0 +1,57 @@ +# Migration Notes: Java 8 → Java 11 + +## Java Version Change + +- **Source**: Java 8 (1.8) +- **Target**: Java 11 + +Java 11 is a Long-Term Support (LTS) release that introduces improvements such as local-variable type inference (`var`), new String methods, HTTP Client API, and enhanced garbage collection. All existing Java 8 features (Streams, Lambdas, NIO, Optional, LocalDateTime) remain fully compatible. + +## Spring Boot Version Upgrade + +- **From**: Spring Boot 2.0.2.RELEASE +- **To**: Spring Boot 2.7.18 + +Spring Boot 2.7.x is the final 2.x LTS line and provides full Java 11 support, along with security patches, dependency updates, and improved auto-configuration. + +## Build Tool Changes + +### Maven (`pom.xml`) + +- `` changed from `1.8` to `11` +- Added `11` +- Added `UTF-8` +- Changed `` from `pom` to `jar` +- Spring Boot parent version updated to `2.7.18` + +### Gradle (`build.gradle`) + +- `sourceCompatibility` changed from `1.8` to `JavaVersion.VERSION_11` +- `targetCompatibility` changed from `1.8` to `JavaVersion.VERSION_11` +- Spring Boot Gradle plugin updated to `2.7.18` +- Replaced deprecated `compile` configuration with `implementation` +- Replaced deprecated `testCompile` configuration with `testImplementation` +- Added `spring-boot-starter-jdbc` and `h2` dependencies to match Maven + +## Plugin Upgrades + +| Plugin | Version | Purpose | +|--------|---------|---------| +| `maven-compiler-plugin` | 3.11.0 | Compiles with `--release 11` flag | +| `maven-surefire-plugin` | 3.2.5 | Runs unit tests | +| `maven-failsafe-plugin` | 3.2.5 | Runs integration tests | +| `maven-enforcer-plugin` | 3.5.0 | Enforces minimum Java 11 at build time | + +## New Test Coverage + +- **`ApplicationTests.java`**: Spring Boot integration test verifying the application context loads successfully. +- **`TopicControllerTest.java`**: MockMvc-based tests for all REST endpoints (`GET /topic`, `POST /topic`, `PUT /topic/{id}`, `DELETE /topic/{id}`, `GET /`, `GET /datetime`, etc.). +- **`TopicServiceTest.java`**: Unit tests for service-layer methods covering Stream operations, NIO file methods, String processing, sorting, and filtering. + +## CI Workflow Added + +A GitHub Actions CI workflow (`.github/workflows/ci.yml`) was added: +- Triggers on push and pull request to `main` +- Uses `actions/setup-java@v4` with Temurin JDK 11 +- Caches Maven dependencies +- Runs `mvn -B clean verify` and `mvn -B test` diff --git a/README.md b/README.md index 2b09400..ac06947 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # springboot-java8 -The project is made on spring boot. The project summarize the new features present in Java 8. +The project is made on spring Boot. The project summarize the new features present in Java 8. It contain list of harcoded topics list. You can call the apis's with POSTMAN to add,delete,update Topic list In addition, it uses 1) Java 8 NIO methods @@ -18,7 +18,41 @@ In addition, it uses ## Getting Started 1) Download or clone the project with link -(https://github.com/RehmanMuradAli/springboot-java8/) +(https://github.com/COG-GTM/springboot-java8) + +## Prerequisites + +- **JDK 11** or later +- **Maven 3.6+** or **Gradle 6+** +- **POSTMAN** (optional, for API testing) + +## Build & Run + +### With Maven +```bash +mvn clean package +mvn spring-boot:run +``` + +### With Gradle +```bash +./gradlew build +./gradlew bootRun +``` + +The application starts on `http://localhost:8080` by default. + +## Running Tests + +### With Maven +```bash +mvn clean test +``` + +### With Gradle +```bash +./gradlew test +``` ## Available API's @@ -74,23 +108,6 @@ GET /datetime -### Prerequisites - -1) Java sdk -2) POSTMAN - -### Installing - - - -``` -1) Download or clone -2) Import the project -3) Run on location machine -4) Open Postman, to call API's ( localhost:8080 ) -``` - - ## Helpful Links Spring: @@ -130,5 +147,3 @@ https://dzone.com/articles/java-8-friday-goodies-new-new ## Authors * **Rehman Murad Ali** - - diff --git a/build.gradle b/build.gradle index 09f1083..35f8cec 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() } dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.2.RELEASE") + classpath("org.springframework.boot:spring-boot-gradle-plugin:2.7.18") } } @@ -22,11 +22,12 @@ repositories { mavenCentral() } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +sourceCompatibility = JavaVersion.VERSION_11 +targetCompatibility = JavaVersion.VERSION_11 dependencies { - compile("org.springframework.boot:spring-boot-starter-web") - testCompile("junit:junit") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-jdbc") + implementation("com.h2database:h2") + testImplementation("org.springframework.boot:spring-boot-starter-test") } - diff --git a/pom.xml b/pom.xml index 63f5cbd..d3a773f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,13 +5,13 @@ org.springframework gs-spring-boot - pom + jar 0.1.0 org.springframework.boot spring-boot-starter-parent - 2.0.2.RELEASE + 2.7.18 @@ -32,19 +32,63 @@ com.h2database h2 + + org.springframework.boot + spring-boot-starter-test + test + - 1.8 + 11 + 11 + UTF-8 - org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 11 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.2.5 + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.5.0 + + + enforce-java + + enforce + + + + + [11,) + + + + + + diff --git a/src/main/java/hello/Application.java b/src/main/java/hello/Application.java index 7cf8faf..b72d4c6 100644 --- a/src/main/java/hello/Application.java +++ b/src/main/java/hello/Application.java @@ -47,6 +47,7 @@ public RestTemplate restTemplate(RestTemplateBuilder builder) { } @Bean + @org.springframework.context.annotation.Profile("!test") public CommandLineRunner run(RestTemplate restTemplate) throws Exception { return args -> { Quote quote = restTemplate.getForObject( diff --git a/src/test/java/hello/ApplicationTests.java b/src/test/java/hello/ApplicationTests.java new file mode 100644 index 0000000..094b69e --- /dev/null +++ b/src/test/java/hello/ApplicationTests.java @@ -0,0 +1,12 @@ +package hello; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApplicationTests { + + @Test + void contextLoads() { + } +} diff --git a/src/test/java/hello/controller/TopicControllerTest.java b/src/test/java/hello/controller/TopicControllerTest.java new file mode 100644 index 0000000..125876b --- /dev/null +++ b/src/test/java/hello/controller/TopicControllerTest.java @@ -0,0 +1,112 @@ +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.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +class TopicControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void getAllTopics_returnsTopicList() throws Exception { + mockMvc.perform(get("/topic")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(greaterThanOrEqualTo(3)))) + .andExpect(jsonPath("$[*].id", hasItems("spring", "java", "javascript"))); + } + + @Test + void getTopicWithID_returnsSingleTopic() throws Exception { + mockMvc.perform(get("/topic/java")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is("java"))) + .andExpect(jsonPath("$.subjectName", is("Core Java"))); + } + + @Test + void addTopic_createsNewTopic() throws Exception { + String topicJson = "{\"id\":\"python\",\"subjectName\":\"Python Language\",\"subjectDescription\":\"Python Description\"}"; + + mockMvc.perform(post("/topic") + .contentType(MediaType.APPLICATION_JSON) + .content(topicJson)) + .andExpect(status().isOk()); + + mockMvc.perform(get("/topic/python")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id", is("python"))); + } + + @Test + void updateTopic_updatesExistingTopic() throws Exception { + String topicJson = "{\"id\":\"spring\",\"subjectName\":\"Spring Boot\",\"subjectDescription\":\"Updated Description\"}"; + + mockMvc.perform(put("/topic/spring") + .contentType(MediaType.APPLICATION_JSON) + .content(topicJson)) + .andExpect(status().isOk()); + + mockMvc.perform(get("/topic/spring")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subjectName", is("Spring Boot"))); + } + + @Test + void deleteTopic_removesTopicFromList() throws Exception { + String topicJson = "{\"id\":\"delete-me\",\"subjectName\":\"Temp\",\"subjectDescription\":\"Temp Desc\"}"; + + mockMvc.perform(post("/topic") + .contentType(MediaType.APPLICATION_JSON) + .content(topicJson)) + .andExpect(status().isOk()); + + mockMvc.perform(delete("/topic/delete-me")) + .andExpect(status().isOk()); + } + + @Test + void filterMinimumLengthForId_returnsFilteredTopics() throws Exception { + mockMvc.perform(get("/topic/minimum/length/4")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[*].id", everyItem(not(is("java"))))); + } + + @Test + void sortTopicsWithID_returnsSortedTopics() throws Exception { + mockMvc.perform(get("/topic/sort")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id", lessThanOrEqualTo("java"))); + } + + @Test + void greetingEndpoint_returnsGreeting() throws Exception { + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content", is("Hello, World!"))); + } + + @Test + void greetingEndpoint_withName_returnsCustomGreeting() throws Exception { + mockMvc.perform(get("/").param("name", "Spring")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content", is("Hello, Spring!"))); + } + + @Test + void datetimeEndpoint_returnsDateTimeInfo() throws Exception { + mockMvc.perform(get("/datetime")) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("Greetings from Spring Boot!"))); + } +} diff --git a/src/test/java/hello/service/TopicServiceTest.java b/src/test/java/hello/service/TopicServiceTest.java new file mode 100644 index 0000000..0fa4ffd --- /dev/null +++ b/src/test/java/hello/service/TopicServiceTest.java @@ -0,0 +1,107 @@ +package hello.service; + +import hello.model.Topic; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class TopicServiceTest { + + private TopicService topicService; + + @BeforeEach + void setUp() { + topicService = new TopicService(); + } + + @Test + void getAllTopics_returnsDefaultTopics() { + List topics = topicService.getAllTopics(); + assertEquals(3, topics.size()); + assertEquals("spring", topics.get(0).getId()); + assertEquals("java", topics.get(1).getId()); + assertEquals("javascript", topics.get(2).getId()); + } + + @Test + void getTopicWithId_returnsCorrectTopic() { + Topic topic = topicService.getTopicWithId("java"); + assertNotNull(topic); + assertEquals("java", topic.getId()); + assertEquals("Core Java", topic.getSubjectName()); + } + + @Test + void addTopic_increasesListSize() { + Topic newTopic = new Topic("python", "Python", "Python Description"); + topicService.addTopic(newTopic); + assertEquals(4, topicService.getAllTopics().size()); + } + + @Test + void updateTopic_changesExistingTopic() { + Topic updated = new Topic("java", "Java Updated", "Updated Description"); + topicService.updateTopic("java", updated); + Topic result = topicService.getTopicWithId("java"); + assertEquals("Java Updated", result.getSubjectName()); + } + + @Test + void deleteTopic_removesFromList() { + topicService.deleteTopic("java"); + assertEquals(2, topicService.getAllTopics().size()); + } + + @Test + void filterMinimumLengthForId_filtersCorrectly() { + List filtered = topicService.filterMinimumLengthForId(4); + assertTrue(filtered.stream().allMatch(t -> t.getId().length() > 4)); + } + + @Test + void sortTopicsWithID_sortsByIdAscending() { + List sorted = topicService.sortTopicsWithID(); + for (int i = 0; i < sorted.size() - 1; i++) { + assertTrue(sorted.get(i).getId().compareTo(sorted.get(i + 1).getId()) <= 0); + } + } + + @Test + void returnAllTopicIDWithStringSlicing_joinsIdsWithColon() { + String result = topicService.returnAllTopicIDWithStringSlicing(); + assertEquals("spring:java:javascript", result); + } + + @Test + void makeDistinctAndSortCharacters_returnsDistinctSortedChars() { + String result = topicService.makeDistinctAndSortCharacters("aabbcc"); + assertEquals("abc", result); + } + + @Test + void splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoin_filtersJavaKeyword() { + String result = topicService.splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoin("spring:java:javascript"); + assertEquals("java:javascript", result); + } + + @Test + void findIdHavingCharacter_returnsIdsWithG() { + String result = topicService.findIdHavingCharacter(); + assertTrue(result.contains("spring")); + } + + @Test + void findAllFilesInPathAndSort_returnsFileList() { + String result = topicService.findAllFilesInPathAndSort(); + assertNotNull(result); + } + + @Test + void readFileWithStreamFunction_handlesFileGracefully() { + String result = topicService.readFileWithStreamFunction(); + assertNotNull(result); + } +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..7f09319 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,2 @@ +spring.profiles.active=test +spring.main.allow-bean-definition-overriding=true