diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1199134..187afac 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,31 +1,37 @@
-name: Software-Development-Simulation-Main-Build
+name: Build and Analyze with SonarQube
on:
push:
branches:
- main
+ pull_request:
+ branches:
+ - main
jobs:
build:
- name: Build and analyze
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - name: Checkout code
+ uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 22
- uses: actions/setup-java@v1
+ uses: actions/setup-java@v4
with:
java-version: 22
+ distribution: 'corretto'
- name: Cache SonarQube packages
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
+ - name: Pre-fetch Maven dependencies
+ run: mvn dependency:go-offline
- name: Cache Maven packages
- uses: actions/cache@v1
+ uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
@@ -34,4 +40,4 @@ jobs:
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
- run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=Software-Development-Simulation -Dsonar.projectName='Software Development Simulation' -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
\ No newline at end of file
+ run: mvn clean install -B verify sonar:sonar
\ No newline at end of file
diff --git a/README.md b/README.md
index 45db103..4990309 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
![SonarQube Quality Gate](https://img.shields.io/badge/SonarQube%20Quality%20Gate-Passed-brightgreen)
![SonarQube Duplicated Lines](https://img.shields.io/badge/SonarQube%20Duplicated%20Lines-0%25-brightgreen)
![SonarQube LOC](https://img.shields.io/badge/SonarQube%20LOC-2000-blue)
-![JaCoCo Coverage](https://img.shields.io/badge/JaCoCo%20Coverage-93.1%25-brightgreen)
+![JaCoCo Coverage](https://img.shields.io/badge/JaCoCo%20Coverage-95.8%25-brightgreen)
**Note:** SonarQube information is based on the last GitHub Action run and is generated locally. As such, there is no direct link available to the SonarQube dashboard.
@@ -13,6 +13,23 @@
The Software Development Simulation project is a web-based application designed to simulate and manage software development tasks. It utilizes Spring Boot for backend services, Spring Integration for messaging, and integrates with Swagger for API documentation.
+## Usage guide
+
+This application is to be used from web interface that has two views:
+
+1. Main view on index page, with fallowing features:
+ - Allows user input for desired number of randomly generated development Epics, which is given as range between MIN - MAX (to input exact number user should input same value for both).
+ - Allows saving those generated epics along with current development teams setup for future use, or manual change.
+ - Allows managing previously saved, i.e. predefined epics, and consequently user stories and technical tasks, with option to save without overriding previous data.
+ - Allows addition, editing and removal of epics, user stories and technical tasks in currently loaded predefined data.
+ - Provides view of informational output, error output as well as jira stream generated output during application flow.
+ - Provides options to change to "Developers page" view.
+2. Developers page view, with fallowing features
+ - View of current development teams setups and details about each developer (their personal data, skill level, position, etc.).
+ - Allows managing of said data through editing developer data, adding, moving or removing individual developers from their development teams.
+ - Provide options to generate new batch of developers data with random data using predefined parameters (male/female gender ratio, MIN - MAX range of total possible number of developers and MIN - MAX possible number of developers assigned in each development team).
+ - Provides option to add previously mentioned generated developers data to existing or to create fresh data (by checking option to not retain previous development team setup)
+
## Features
- **Spring Boot Application**: Built with Spring Boot 3.3.5.
diff --git a/pom.xml b/pom.xml
index 79dcdb3..306b78d 100755
--- a/pom.xml
+++ b/pom.xml
@@ -37,11 +37,23 @@
UTF-8
- 3.3.5
- 6.3.5
- 1.49.2
- 5.11.3
+ 3.3.5
+ 6.3.5
+ 1.55.0
+ 5.11.3
+ 5.14.2
${project.version}
+ 0.8.12
+ Software-Development-Simulation
+ Software Development Simulation
+ ${application.version}
+ ${env.SONAR_HOST_URL}
+ ${env.SONAR_TOKEN}
+ target/classes
+
+ src/main/java/dev/markodojkic/softwaredevelopmentsimulation/SoftwareDevelopmentSimulationApp.java
+
+ reuseReports
@@ -49,38 +61,38 @@
org.springframework.boot
spring-boot
- ${spring-version}
+ ${spring.version}
compile
org.springframework.boot
spring-boot-autoconfigure
- ${spring-version}
+ ${spring.version}
compile
true
org.springframework.boot
spring-boot-starter-thymeleaf
- ${spring-version}
+ ${spring.version}
compile
org.springframework.boot
spring-boot-devtools
- ${spring-version}
+ ${spring.version}
true
org.springframework.boot
spring-boot-starter-web
- ${spring-version}
+ ${spring.version}
compile
org.springframework.boot
spring-boot-test-autoconfigure
- ${spring-version}
+ ${spring.version}
test
@@ -88,25 +100,25 @@
org.springframework.integration
spring-integration-core
- ${spring-integration-version}
+ ${spring-integration.version}
compile
org.springframework.integration
spring-integration-stream
- ${spring-integration-version}
+ ${spring-integration.version}
compile
org.springframework.integration
spring-integration-mqtt
- ${spring-integration-version}
+ ${spring-integration.version}
compile
org.springframework.integration
spring-integration-file
- ${spring-integration-version}
+ ${spring-integration.version}
compile
@@ -159,37 +171,37 @@
com.github.hazendaz.jmockit
jmockit
- ${jmockit-version}
+ ${jmockit.version}
test
org.junit.jupiter
junit-jupiter
- ${junit-jupiter-version}
+ ${junit-jupiter.version}
test
org.junit.jupiter
junit-jupiter-api
- ${junit-jupiter-version}
+ ${junit-jupiter.version}
test
org.junit.jupiter
junit-jupiter-engine
- ${junit-jupiter-version}
+ ${junit-jupiter.version}
test
org.junit.platform
- junit-platform-suite-api
+ junit-platform-suite
1.11.3
test
org.mockito
mockito-junit-jupiter
- 5.14.2
+ ${mockito.version}
test
@@ -230,7 +242,7 @@
org.springframework.boot
spring-boot-maven-plugin
- ${spring-version}
+ ${spring.version}
dev.markodojkic.softwaredevelopmentsimulation.SoftwareDevelopmentSimulationApp
JAR
@@ -252,26 +264,27 @@
22
+
+ org.sonarsource.scanner.maven
+ sonar-maven-plugin
+ 5.0.0.4389
+
org.jacoco
jacoco-maven-plugin
- 0.8.12
+ ${jacoco.version}
prepare-agent
-
-
-
- report
- test
-
report
+ test
dev/markodojkic/softwaredevelopmentsimulation/SoftwareDevelopmentSimulationApp.class
+ ${project.build.directory}/site/jacoco/jacoco.xml
@@ -282,18 +295,27 @@
3.5.1
- ${argLine}
-Dspring.profiles.active=test
-Xshare:off
-XX:+EnableDynamicAgentLoading
- -javaagent:${user.home}/.m2/repository/com/github/hazendaz/jmockit/jmockit/${jmockit-version}/jmockit-${jmockit-version}.jar
+ -javaagent:${settings.localRepository}/com/github/hazendaz/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
+ -javaagent:${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar
+ -javaagent:${settings.localRepository}/org/jacoco/org.jacoco.agent/${jacoco.version}/org.jacoco.agent-${jacoco.version}-runtime.jar=destfile=target/jacoco.exec
+ dev/markodojkic/softwaredevelopmentsimulation/test/SoftwareDevelopmentSimulationAppTestsSuite
org.codehaus.mojo
versions-maven-plugin
2.17.1
+
+
+
+ use-latest-versions
+
+
+
diff --git a/src/main/java/dev/markodojkic/softwaredevelopmentsimulation/model/BaseTask.java b/src/main/java/dev/markodojkic/softwaredevelopmentsimulation/model/BaseTask.java
index e046619..5cbc6ac 100644
--- a/src/main/java/dev/markodojkic/softwaredevelopmentsimulation/model/BaseTask.java
+++ b/src/main/java/dev/markodojkic/softwaredevelopmentsimulation/model/BaseTask.java
@@ -40,10 +40,10 @@ public String toString() {
}
public void setAssignee(Developer assignee) {
- this.assignee = this.assignee == null ? assignee : this.assignee;
+ this.assignee = assignee == null ? this.assignee : assignee;
}
public void setReporter(Developer reporter) {
- this.reporter = this.reporter == null ? reporter : this.reporter;
+ this.reporter = reporter == null ? this.reporter : reporter;
}
}
\ No newline at end of file
diff --git a/src/main/java/dev/markodojkic/softwaredevelopmentsimulation/util/Utilities.java b/src/main/java/dev/markodojkic/softwaredevelopmentsimulation/util/Utilities.java
index 2730503..db2af2a 100644
--- a/src/main/java/dev/markodojkic/softwaredevelopmentsimulation/util/Utilities.java
+++ b/src/main/java/dev/markodojkic/softwaredevelopmentsimulation/util/Utilities.java
@@ -66,12 +66,11 @@ public class Utilities {
static {
boolean isTesting = System.getProperty("spring.profiles.active", "default").equals("test");
- Path base = isTesting ? Paths.get("src/test/resources", "dev.markodojkic.software_development_simulation.testing_data") : Paths.get(System.getProperty("user.home"), "dev.markodojkic", "software_development_simulation", "1.4.0");
+ currentApplicationDataPath = isTesting ? Paths.get("src/test/resources", "dev.markodojkic.software_development_simulation.testing_data") : Paths.get(System.getProperty("user.home"), "dev.markodojkic", "software_development_simulation", "1.4.0");
- currentApplicationDataPath = base;
- currentApplicationLogsPath = Paths.get(String.valueOf(base), "logs", isTesting ? "2012-12-12 00-00-00" : ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss")));
+ currentApplicationLogsPath = Paths.get(String.valueOf(currentApplicationDataPath), "logs", isTesting ? "2012-12-12 00-00-00" : ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH-mm-ss")));
- if(isTesting){
+ if(!Files.exists(currentApplicationDataPath)){
try {
Files.createDirectories(currentApplicationDataPath);
Files.createDirectories(currentApplicationLogsPath);
diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt
index 3e9a332..1a33a62 100644
--- a/src/main/resources/banner.txt
+++ b/src/main/resources/banner.txt
@@ -16,5 +16,5 @@
|___/_|_| |_| |_|\__,_|_|\__,_|\__\___/|_|
Application version: ${application.version}
-Powered by Spring Boot (v${spring-version}) and Spring Integration (v${spring-integration-version})
+Powered by Spring Boot (v${spring.version}) and Spring Integration (v${spring-integration.version})
Ⓒ Marko Dojkić 2024
\ No newline at end of file
diff --git a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/BaseTaskTest.java b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/BaseTaskTest.java
index a8c25fa..c8a6d33 100644
--- a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/BaseTaskTest.java
+++ b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/BaseTaskTest.java
@@ -1,5 +1,6 @@
package dev.markodojkic.softwaredevelopmentsimulation.test;
+import com.fasterxml.jackson.core.JsonProcessingException;
import dev.markodojkic.softwaredevelopmentsimulation.enums.Priority;
import dev.markodojkic.softwaredevelopmentsimulation.enums.DeveloperType;
import dev.markodojkic.softwaredevelopmentsimulation.model.BaseTask;
@@ -8,7 +9,9 @@
import org.junit.jupiter.api.Test;
import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import static dev.markodojkic.softwaredevelopmentsimulation.util.Utilities.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -25,6 +28,11 @@ void when_noArgsConstructorIsCalled_correctValuesAreSetAsDefault() {
assertNull(task.getAssignee());
assertNull(task.getReporter());
assertNull(task.getCreatedOn());
+
+ // Assertion for toString method
+ String expectedToString = "BaseTask{id='null', name='null', description='null', priority=null, assignee='UNASSIGNED', reporter='UNASSIGNED', createdOn=null}";
+
+ assertEquals(expectedToString, task.toString());
}
@Test
@@ -82,6 +90,7 @@ void when_equalsOrHashCodeIsCalled_onEqualObjectAreSame_onNonEqualObjectsAreDiff
// Test equality
assertEquals(task1, task2);
assertEquals(task1.hashCode(), task2.hashCode());
+ assertNotEquals(task1, assignee1);
// Create tasks with different IDs
BaseTask task3 = new BaseTask("2", "Task Name", "Task Description", Priority.NORMAL, assignee1, reporter1, ZonedDateTime.now());
@@ -134,6 +143,15 @@ void when_gettersAndSettersAreCalled_valuesAreCorrectlyRetrievedOrSet() {
assertEquals(Priority.CRITICAL, task.getPriority());
assertEquals("assignee 1", task.getAssignee().getDisplayName());
assertEquals("reporter 1", task.getReporter().getDisplayName());
+ task.setAssignee(null);
+ task.setReporter(null);
+ assertEquals("assignee 1", task.getAssignee().getDisplayName());
+ assertEquals("reporter 1", task.getReporter().getDisplayName());
assertNotNull(task.getCreatedOn());
}
+
+ @Test
+ void when_emptyListIsPassedToJSONSerializer_emptyJSONArrayIsRetrieved() throws JsonProcessingException {
+ assertEquals("[]", getObjectMapper().writeValueAsString(new ArrayList<>()));
+ }
}
\ No newline at end of file
diff --git a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/Config/SoftwareDevelopmentSimulationAppBaseTest.java b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/Config/SoftwareDevelopmentSimulationAppBaseTest.java
index 3b61bb5..7e8e881 100644
--- a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/Config/SoftwareDevelopmentSimulationAppBaseTest.java
+++ b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/Config/SoftwareDevelopmentSimulationAppBaseTest.java
@@ -37,7 +37,6 @@
@SpringBootTest
@AutoConfigureMockMvc(addFilters = false) // Enables MockMvc with full context
@ContextConfiguration(classes = { MiscellaneousConfig.class, TestConfig.class, SpringIntegrationMessageChannelsConfig.class, MQTTFlow.class, PrintoutFlow.class, FileHandlingFlow.class, PrinterTransformer.class, DeveloperImpl.class, ProjectManagerImpl.class, MainController.class, DevelopersPageController.class })
-//@ExtendWith({MockitoExtension.class, GlobalSetupExtension.class})
public abstract class SoftwareDevelopmentSimulationAppBaseTest {
private static Server mqttServer;
diff --git a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/DeveloperTest.java b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/DeveloperTest.java
index 08738a6..86286fc 100644
--- a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/DeveloperTest.java
+++ b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/DeveloperTest.java
@@ -44,7 +44,7 @@ void when_allArgsConstructorIsCalled_correctValuesAreSet() {
}
@Test
- void testGetDisplayName() {
+ void when_getDisplayNameIsCalled_correctDisplayNameIsReturned() {
developer.setName("Alice");
developer.setSurname("Johnson");
@@ -52,7 +52,7 @@ void testGetDisplayName() {
}
@Test
- void testToString() {
+ void when_toStringIsCalled_correctDataIsReturned() {
developer = new Developer("123", "Bob", "Smith", "9876543210987", "Test area", DeveloperType.INTERN_DEVELOPER, 1L, false);
String expectedString = "Developer(id=123, name=Bob, surname=Smith, yugoslavianUMCN=9876543210987, placeOfBirth=Test area, developerType=INTERN_DEVELOPER, experienceCoefficient=1)";
diff --git a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/SoftwareDevelopmentSimulationAppTest.java b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/SoftwareDevelopmentSimulationAppTest.java
index 6aaa65e..214d9d5 100644
--- a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/SoftwareDevelopmentSimulationAppTest.java
+++ b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/SoftwareDevelopmentSimulationAppTest.java
@@ -30,6 +30,7 @@
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import static dev.markodojkic.softwaredevelopmentsimulation.util.DataProvider.*;
import static dev.markodojkic.softwaredevelopmentsimulation.util.Utilities.*;
@@ -103,11 +104,10 @@ public Message> preSend(Message> message, MessageChannel channel) {
@Test
void when_generateRandomEpics_epicsAreCorrectlyCreated() throws Exception {
- String originalOsName = System.getProperty("os.name");
- System.setProperty("os.name", "generic");
assertNotNull(epicMessageInput);
assertNotNull(doneEpicsOutput);
+ updateDevelopmentTeamsSetup(new DevelopmentTeamCreationParameters(65, 30, 31, 15, 20, false));
CountDownLatch epicMessageInputLatch = new CountDownLatch(4);
CountDownLatch epicMessageDoneLatch = new CountDownLatch(4);
int epicCountDownLimit = 4;
@@ -170,23 +170,32 @@ public Message> preSend(Message> message, MessageChannel channel) {
assertFalse(Files.exists(getCurrentApplicationDataPath().resolve("predefinedData").resolve("2012-12-12 00-00-00").resolve("sessionData.json")));
assertFalse(Files.exists(getCurrentApplicationDataPath().resolve("predefinedData").resolve("2012-12-12 00-00-00").resolve("developersData.json")));
-
- System.setProperty("os.name", originalOsName);
}
@Test
void when_generateRandomEpicsWithSave_epicsAreCorrectlyCreatedAndSaved() throws Exception {
Uninterruptibles.sleepUninterruptibly(3, TimeUnit.SECONDS);
- String originalOsName = System.getProperty("os.name");
- System.setProperty("os.name", "generic");
assertNotNull(doneEpicsOutput);
- CountDownLatch epicMessageDoneLatch = new CountDownLatch(1);
+ CountDownLatch epicMessageDoneLatch;
int epicCountDownLimit = 1;
- int epicCountUpperLimit = 1;
+ int epicCountUpperLimit = 2;
+ AtomicInteger countOfEpics = new AtomicInteger(0);
mockMvc.perform(post("/api/applicationFlowRandomized").param("save", "true").param("min", String.valueOf(epicCountDownLimit)).param("max", String.valueOf(epicCountUpperLimit))).andExpect(status().is2xxSuccessful());
+ ((PriorityChannel) epicMessageInput).addInterceptor(new ExecutorChannelInterceptor() {
+ @Override
+ public Message> preSend(Message> message, MessageChannel channel) {
+ countOfEpics.incrementAndGet();
+ return ExecutorChannelInterceptor.super.preSend(message, channel);
+ }
+ });
+
+ Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
+
+ epicMessageDoneLatch = new CountDownLatch(countOfEpics.get());
+
((DirectChannel) doneEpicsOutput).addInterceptor(new ExecutorChannelInterceptor() {
@Override
public Message> preSend(Message> message, MessageChannel channel) {
@@ -206,8 +215,6 @@ public Message> preSend(Message> message, MessageChannel channel) {
assertFalse(Files.readString(getCurrentApplicationLogsPath().resolve("jiraActivityStreamChannel.log")).isEmpty());
assertFalse(Files.readString(getCurrentApplicationDataPath().resolve("predefinedData").resolve("2012-12-12 00-00-00").resolve("sessionData.json")).isEmpty());
assertFalse(Files.readString(getCurrentApplicationDataPath().resolve("predefinedData").resolve("2012-12-12 00-00-00").resolve("developersData.json")).isEmpty());
-
- System.setProperty("os.name", originalOsName);
}
@Test
@@ -297,7 +304,10 @@ public Message> preSend(Message> message, MessageChannel channel) {
.andExpect(status().is2xxSuccessful())
.andExpect(MockMvcResultMatchers.content().string("Data successfully saved to folder '2012-12-12 00-00-00'"));
+ assertEquals(Objects.requireNonNull(getObjectMapper().writeValueAsString(epicsInput)), Objects.requireNonNull(getObjectMapper().writeValueAsString(epicsDone))); //Direct test for epics custom json serializer/deserializer
+
assertEquals(Files.readString(Paths.get(Objects.requireNonNull(getClass().getClassLoader().getResource("testSessionData.json")).toURI())), Files.readString(getCurrentApplicationDataPath().resolve("predefinedData").resolve("2012-12-12 00-00-00").resolve("sessionData.json")));
+
assertEquals(Files.readString(Paths.get(Objects.requireNonNull(getClass().getClassLoader().getResource("testDevelopersData.json")).toURI())), Files.readString(getCurrentApplicationDataPath().resolve("predefinedData").resolve("2012-12-12 00-00-00").resolve("developersData.json")));
} catch (Exception e) {
fail(e.getCause());
diff --git a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/SwaggerTest.java b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/SwaggerTest.java
index 8bc6db3..62c5407 100644
--- a/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/SwaggerTest.java
+++ b/src/test/java/dev/markodojkic/softwaredevelopmentsimulation/test/SwaggerTest.java
@@ -25,7 +25,7 @@ class SwaggerTest {
private GroupedOpenApi groupedOpenApi;
@Test
- void testCustomOpenAPIConfig() {
+ void when_openAPIConfigIsCalled_correctDataIsReturned() {
Info info = openAPI.getInfo();
assertEquals("Software development simulator™ API", info.getTitle());
assertEquals("This is the API documentation for the Software development simulator™ Developed by Ⓒ Marko Dojkić", info.getDescription());
@@ -41,7 +41,7 @@ void testCustomOpenAPIConfig() {
}
@Test
- void testApiGroupConfig() {
+ void when_groupedOpenAPIConfigIsCalled_correctDataIsReturned() {
// Verify that the GroupedOpenApi is set up with the correct group name and paths
assertEquals("api", groupedOpenApi.getGroup());
assertEquals(List.of("/api/**"), groupedOpenApi.getPathsToMatch());