diff --git a/.github/workflows/backend-test-action.yml b/.github/workflows/backend-test-action.yml
index 50f40c9fb2..c31d16410f 100644
--- a/.github/workflows/backend-test-action.yml
+++ b/.github/workflows/backend-test-action.yml
@@ -10,11 +10,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- - name: Set up JDK 17
+ - name: Set up JDK ${{vars.JAVA_VERSION}}
uses: actions/setup-java@v4
with:
- java-version: '17'
+ java-version: ${{vars.JAVA_VERSION}}
distribution: 'adopt'
- name: Use Maven to run unittests and integration tests
- run: mvn clean verify
\ No newline at end of file
+ run: mvn clean verify -X
\ No newline at end of file
diff --git a/.github/workflows/demo-deploy-action.yml b/.github/workflows/demo-deploy-action.yml
index b237b77150..59102f9846 100644
--- a/.github/workflows/demo-deploy-action.yml
+++ b/.github/workflows/demo-deploy-action.yml
@@ -50,10 +50,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Set up JDK 17
+ - name: Set up JDK ${{vars.JAVA_VERSION}}
uses: actions/setup-java@v4
with:
- java-version: '17'
+ java-version: ${{vars.JAVA_VERSION}}
distribution: 'adopt'
- name: Set up node 18
diff --git a/.github/workflows/deploy-action.yml b/.github/workflows/deploy-action.yml
index d3f81979b6..44dee585c9 100644
--- a/.github/workflows/deploy-action.yml
+++ b/.github/workflows/deploy-action.yml
@@ -26,10 +26,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Set up JDK 17
+ - name: Set up JDK ${{vars.JAVA_VERSION}}
uses: actions/setup-java@v4
with:
- java-version: '17'
+ java-version: ${{vars.JAVA_VERSION}}
distribution: 'adopt'
- name: Set up node 18
diff --git a/.github/workflows/format-action.yml b/.github/workflows/format-action.yml
index bf3d25bd7f..8ab0c9dd70 100644
--- a/.github/workflows/format-action.yml
+++ b/.github/workflows/format-action.yml
@@ -46,10 +46,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- - name: Set up JDK 17
+ - name: Set up JDK ${{vars.JAVA_VERSION}}
uses: actions/setup-java@v4
with:
- java-version: '17'
+ java-version: ${{vars.JAVA_VERSION}}
distribution: 'adopt'
server-id: github
settings-path: ${{github.workspace}}
diff --git a/.github/workflows/frontend-test-action.yml b/.github/workflows/frontend-test-action.yml
index 73c35e400c..2f34b3596b 100644
--- a/.github/workflows/frontend-test-action.yml
+++ b/.github/workflows/frontend-test-action.yml
@@ -28,10 +28,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- - name: Set up JDK 17
+ - name: Set up JDK ${{vars.JAVA_VERSION}}
uses: actions/setup-java@v4
with:
- java-version: '17'
+ java-version: ${{vars.JAVA_VERSION}}
distribution: 'adopt'
- uses: abhi1693/setup-browser@v0.3.5
diff --git a/.github/workflows/staging-deploy-action.yml b/.github/workflows/staging-deploy-action.yml
index 1a4495dcd9..07d18713e1 100644
--- a/.github/workflows/staging-deploy-action.yml
+++ b/.github/workflows/staging-deploy-action.yml
@@ -50,10 +50,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Set up JDK 17
+ - name: Set up JDK ${{vars.JAVA_VERSION}}
uses: actions/setup-java@v4
with:
- java-version: '17'
+ java-version: ${{vars.JAVA_VERSION}}
distribution: 'adopt'
- name: Set up node
diff --git a/.run/OkrApplication-debug-docker.run.xml b/.run/OkrApplication-debug-docker.run.xml
new file mode 100755
index 0000000000..f4213371b4
--- /dev/null
+++ b/.run/OkrApplication-debug-docker.run.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/pom.xml b/backend/pom.xml
index a4877fbaaa..2e00fc4f7d 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -71,10 +71,6 @@
com.h2databaseh2
-
- org.springframework.boot
- spring-boot-starter-data-ldap
- com.tngtech.archunitarchunit-junit5
@@ -87,6 +83,15 @@
3.26.3test
+
+ org.springframework.boot
+ spring-boot-devtools
+
+
+ org.springframework
+ springloaded
+ 1.2.8.RELEASE
+
@@ -202,8 +207,6 @@
${project.build.directory}/jacoco-output/merged.exec
-
-
@@ -240,6 +243,7 @@
+ code-formatformat
@@ -282,5 +286,37 @@
+
+ debug
+
+
+ org.springframework.boot
+ spring-boot-devtools
+
+
+ org.springframework
+ springloaded
+ 1.2.8.RELEASE
+
+
+
+
+ no-formatter
+
+
+
+ net.revelc.code.formatter
+ formatter-maven-plugin
+ 2.23.0
+
+
+ code-format
+ none
+
+
+
+
+
+
diff --git a/backend/src/main/java/ch/puzzle/okr/controller/QuarterController.java b/backend/src/main/java/ch/puzzle/okr/controller/QuarterController.java
index eb92ef5841..1cc1383dbc 100644
--- a/backend/src/main/java/ch/puzzle/okr/controller/QuarterController.java
+++ b/backend/src/main/java/ch/puzzle/okr/controller/QuarterController.java
@@ -17,7 +17,7 @@
import java.util.List;
@RestController
-@RequestMapping("api/v1/quarters")
+@RequestMapping("api/v2/quarters")
public class QuarterController {
private final QuarterBusinessService quarterBusinessService;
@@ -28,9 +28,17 @@ public QuarterController(QuarterBusinessService quarterBusinessService) {
@Operation(summary = "Get quarters", description = "Get a List of quarters depending on current date")
@ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Returned a List of quarters", content = {
- @Content(mediaType = "application/json", schema = @Schema(implementation = TeamDto.class)) }) })
+ @Content(mediaType = "application/json", schema = @Schema(implementation = Quarter.class)) }) })
@GetMapping("")
public ResponseEntity> getCurrentQuarters() {
return ResponseEntity.status(HttpStatus.OK).body(this.quarterBusinessService.getQuarters());
}
+
+ @Operation(summary = "Get current quarter", description = "Get the current quarter depending on current date")
+ @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Returned the current quarter", content = {
+ @Content(mediaType = "application/json", schema = @Schema(implementation = Quarter.class)) }) })
+ @GetMapping("/current")
+ public ResponseEntity getCurrentQuarter() {
+ return ResponseEntity.status(HttpStatus.OK).body(this.quarterBusinessService.getCurrentQuarter());
+ }
}
diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/CnAttributesMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/CnAttributesMapper.java
deleted file mode 100644
index d315869043..0000000000
--- a/backend/src/main/java/ch/puzzle/okr/mapper/CnAttributesMapper.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package ch.puzzle.okr.mapper;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.ldap.core.AttributesMapper;
-import org.springframework.stereotype.Component;
-
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-
-@Component
-public class CnAttributesMapper implements AttributesMapper {
-
- @Override
- public String mapFromAttributes(Attributes attributes) throws NamingException {
- Attribute cnAttribute = attributes.get("cn");
- if (cnAttribute != null) {
- return cnAttribute.get().toString();
- }
- return null;
- }
-}
diff --git a/backend/src/main/java/ch/puzzle/okr/repository/TeamRepository.java b/backend/src/main/java/ch/puzzle/okr/repository/TeamRepository.java
index 6a792921c5..14e48978ed 100644
--- a/backend/src/main/java/ch/puzzle/okr/repository/TeamRepository.java
+++ b/backend/src/main/java/ch/puzzle/okr/repository/TeamRepository.java
@@ -1,20 +1,11 @@
package ch.puzzle.okr.repository;
import ch.puzzle.okr.models.Team;
-import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
-import org.springframework.data.repository.query.Param;
import java.util.List;
public interface TeamRepository extends CrudRepository {
- @Query(value = """
- select distinct teamOrg.team_id from team_organisation teamOrg
- inner join organisation o on o.id = teamOrg.organisation_id
- where o.org_name in (:organisationNames)
- """, nativeQuery = true)
- List findTeamIdsByOrganisationNames(@Param("organisationNames") List organisationNames);
-
List findTeamsByName(String name);
}
diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java
index 675d8ec249..9e7a2bcd34 100644
--- a/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java
+++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceService.java
@@ -40,6 +40,17 @@ public String getModelName() {
return OBJECTIVE;
}
+ /**
+ * Get the number of Objectives of a Team in a Quarter. The underling sql looks like "select count(*) from objective
+ * where teamId = team.id and quarterId = quarter.id."
+ *
+ * @param team
+ * Team
+ * @param quarter
+ * Quarter
+ *
+ * @return number of Objectives of team in quarter
+ */
public Integer countByTeamAndQuarter(Team team, Quarter quarter) {
return getRepository().countByTeamAndQuarter(team, quarter);
}
diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/TeamPersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/TeamPersistenceService.java
index 45d3eba4ae..533949d848 100644
--- a/backend/src/main/java/ch/puzzle/okr/service/persistence/TeamPersistenceService.java
+++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/TeamPersistenceService.java
@@ -20,14 +20,6 @@ public String getModelName() {
return TEAM;
}
- public List findTeamIdsByOrganisationName(String organisationName) {
- return findTeamIdsByOrganisationNames(List.of(organisationName));
- }
-
- public List findTeamIdsByOrganisationNames(List organisationNames) {
- return getRepository().findTeamIdsByOrganisationNames(organisationNames);
- }
-
public List findTeamsByName(String name) {
return getRepository().findTeamsByName(name);
}
diff --git a/backend/src/main/java/ch/puzzle/okr/util/CollectionUtils.java b/backend/src/main/java/ch/puzzle/okr/util/CollectionUtils.java
new file mode 100644
index 0000000000..5c3a002a28
--- /dev/null
+++ b/backend/src/main/java/ch/puzzle/okr/util/CollectionUtils.java
@@ -0,0 +1,14 @@
+package ch.puzzle.okr.util;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+public class CollectionUtils {
+
+ public static List iterableToList(Iterable iterable) {
+ return StreamSupport //
+ .stream(iterable.spliterator(), false) //
+ .collect(Collectors.toList());
+ }
+}
diff --git a/backend/src/main/java/ch/puzzle/okr/util/quarter/generate/h2/QuarterFunction.java b/backend/src/main/java/ch/puzzle/okr/util/quarter/generate/h2/QuarterFunction.java
index d8c663576c..fec72a232a 100644
--- a/backend/src/main/java/ch/puzzle/okr/util/quarter/generate/h2/QuarterFunction.java
+++ b/backend/src/main/java/ch/puzzle/okr/util/quarter/generate/h2/QuarterFunction.java
@@ -2,6 +2,8 @@
import ch.puzzle.okr.util.quarter.generate.QuarterData;
import ch.puzzle.okr.util.quarter.generate.Quarters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@@ -21,6 +23,7 @@ public class QuarterFunction {
public static final int NEXT_QUARTER_DB_ID = 3;
private static final Map QUARTERS = new HashMap<>();
+ private static final Logger logger = LoggerFactory.getLogger(QuarterFunction.class);
public static void initQuarterData() {
LocalDate now = LocalDate.now();
@@ -37,27 +40,40 @@ private static void registerCurrentQuarterForDate(LocalDate date, int dbId) {
}
public static String currentQuarterLabel() {
- return QUARTERS.get(CURRENT_QUARTER_DB_ID).label();
+ var label = QUARTERS.get(CURRENT_QUARTER_DB_ID).label();
+ logger.info("currentQuarterLabel : {}", label);
+ return label;
+
}
public static String currentQuarterStartDate() {
- return QUARTERS.get(CURRENT_QUARTER_DB_ID).startDateAsIsoString();
+ var start = QUARTERS.get(CURRENT_QUARTER_DB_ID).startDateAsIsoString();
+ logger.info("currentQuarterStartDate: {}", start);
+ return start;
}
public static String currentQuarterEndDate() {
- return QUARTERS.get(CURRENT_QUARTER_DB_ID).endDateAsIsoString();
+ var end = QUARTERS.get(CURRENT_QUARTER_DB_ID).endDateAsIsoString();
+ logger.info("currentQuarterEndDate : {}", end);
+ return end;
}
public static String nextQuarterLabel() {
- return QUARTERS.get(NEXT_QUARTER_DB_ID).label();
+ var label = QUARTERS.get(NEXT_QUARTER_DB_ID).label();
+ logger.info("nextQuarterLabel : {}", label);
+ return label;
}
public static String nextQuarterStartDate() {
- return QUARTERS.get(NEXT_QUARTER_DB_ID).startDateAsIsoString();
+ var start = QUARTERS.get(NEXT_QUARTER_DB_ID).startDateAsIsoString();
+ logger.info("nextQuarterStartDate : {}", start);
+ return start;
}
public static String nextQuarterEndDate() {
- return QUARTERS.get(NEXT_QUARTER_DB_ID).endDateAsIsoString();
+ var end = QUARTERS.get(NEXT_QUARTER_DB_ID).endDateAsIsoString();
+ logger.info("nextQuarterEndDate : {}", end);
+ return end;
}
}
diff --git a/backend/src/main/resources/application-staging.properties b/backend/src/main/resources/application-staging.properties
index 31ce92a3c2..38e4a47571 100644
--- a/backend/src/main/resources/application-staging.properties
+++ b/backend/src/main/resources/application-staging.properties
@@ -1,10 +1,12 @@
# logging level for staging
-logging.level.org.springframework=debug
+logging.level.ch.puzzle.okr=DEBUG
+#logging.level.org.flywaydb.core=DEBUG
+
connect.src=http://localhost:8544 http://localhost:8545
-hibernate.connection.url=jdbc:postgresql://okr-dev-db:5432/okr
+hibernate.connection.url=jdbc:postgresql://localhost:5432/okr
hibernate.connection.username=user
hibernate.connection.password=pwd
hibernate.multiTenancy=SCHEMA
@@ -16,7 +18,7 @@ okr.datasource.driver-class-name=org.postgresql.Driver
okr.user.champion.usernames=peggimann
# pitc
-okr.tenants.pitc.datasource.url=jdbc:postgresql://okr-dev-db:5432/okr
+okr.tenants.pitc.datasource.url=jdbc:postgresql://localhost:5432/okr
okr.tenants.pitc.datasource.username=user
okr.tenants.pitc.datasource.password=pwd
okr.tenants.pitc.datasource.schema=okr_pitc
@@ -27,7 +29,7 @@ okr.tenants.pitc.security.oauth2.frontend.client-id=pitc_okr_staging
# acme
-okr.tenants.acme.datasource.url=jdbc:postgresql://okr-dev-db:5432/okr
+okr.tenants.acme.datasource.url=jdbc:postgresql://localhost:5432/okr
okr.tenants.acme.datasource.username=user
okr.tenants.acme.datasource.password=pwd
okr.tenants.acme.datasource.schema=okr_acme
diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties
index 8f56d0e783..03faf88d85 100644
--- a/backend/src/main/resources/application.properties
+++ b/backend/src/main/resources/application.properties
@@ -17,11 +17,6 @@ logging.level.org.springframework.security=INFO
caching.authorization.users.TTL=1800000
caching.users.TTL=1800000
-spring.ldap.urls=ldap://ldap.puzzle.ch:389
-spring.ldap.base=dc=puzzle,dc=itc
-spring.ldap.username=uid=okrtoolbind,ou=binduser,ou=users,dc=puzzle,dc=itc
-spring.ldap.password=
-
# security configuration
server.port=8080
# server.servlet.context-path=/resource-server
diff --git a/backend/src/test/java/ch/puzzle/okr/SpringCachingConfigTest.java b/backend/src/test/java/ch/puzzle/okr/SpringCachingConfigTest.java
index aa87d01441..1ca5d0825a 100644
--- a/backend/src/test/java/ch/puzzle/okr/SpringCachingConfigTest.java
+++ b/backend/src/test/java/ch/puzzle/okr/SpringCachingConfigTest.java
@@ -6,7 +6,10 @@
import ch.puzzle.okr.service.authorization.AuthorizationRegistrationService;
import ch.puzzle.okr.test.SpringIntegrationTest;
import ch.puzzle.okr.test.TestHelper;
-import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
@@ -14,7 +17,6 @@
import static ch.puzzle.okr.SpringCachingConfig.AUTHORIZATION_USER_CACHE;
import static ch.puzzle.okr.test.TestHelper.defaultUser;
import static org.junit.jupiter.api.Assertions.*;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringIntegrationTest
class SpringCachingConfigTest {
diff --git a/backend/src/test/java/ch/puzzle/okr/controller/QuarterControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/QuarterControllerIT.java
index cfd844139f..5a582cd117 100644
--- a/backend/src/test/java/ch/puzzle/okr/controller/QuarterControllerIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/controller/QuarterControllerIT.java
@@ -7,6 +7,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.BDDMockito;
+import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
@@ -48,7 +49,7 @@ class QuarterControllerIT {
void shouldGetAllQuarters() throws Exception {
BDDMockito.given(quarterBusinessService.getQuarters()).willReturn(quaterList);
- mvc.perform(get("/api/v1/quarters").contentType(MediaType.APPLICATION_JSON))
+ mvc.perform(get("/api/v2/quarters").contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$", Matchers.hasSize(3)))
.andExpect(jsonPath("$[0].id", Is.is(1))).andExpect(jsonPath("$[0].label", Is.is("GJ 22/23-Q2")))
.andExpect(jsonPath("$[0].startDate", Is.is(LocalDate.of(2022, 9, 1).toString())))
@@ -64,7 +65,14 @@ void shouldGetAllQuarters() throws Exception {
void shouldGetAllTeamsIfNoTeamsExists() throws Exception {
BDDMockito.given(quarterBusinessService.getQuarters()).willReturn(Collections.emptyList());
- mvc.perform(get("/api/v1/quarters").contentType(MediaType.APPLICATION_JSON))
+ mvc.perform(get("/api/v2/quarters").contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$", Matchers.hasSize(0)));
}
-}
\ No newline at end of file
+
+ @Test
+ void shouldCallCurrentQuarterAfterRequest() throws Exception {
+ mvc.perform(get("/api/v2/quarters/current").contentType(MediaType.APPLICATION_JSON));
+
+ BDDMockito.verify(quarterBusinessService, Mockito.times(1)).getCurrentQuarter();
+ }
+}
diff --git a/backend/src/test/java/ch/puzzle/okr/mapper/CnAttributesMapperTest.java b/backend/src/test/java/ch/puzzle/okr/mapper/CnAttributesMapperTest.java
deleted file mode 100644
index 20be29cc7c..0000000000
--- a/backend/src/test/java/ch/puzzle/okr/mapper/CnAttributesMapperTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package ch.puzzle.okr.mapper;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.BasicAttributes;
-
-public class CnAttributesMapperTest {
-
- private final CnAttributesMapper cnAttributesMapper = new CnAttributesMapper();
-
- @DisplayName("for Cn AttributeId should return AttributeValue")
- @Test
- void forCnAttributeIdShouldReturnAttributeValue() throws NamingException {
- Attributes attributes = new BasicAttributes();
- attributes.put("cn", "Mango");
-
- Assertions.assertEquals("Mango", cnAttributesMapper.mapFromAttributes(attributes));
- }
-
- @DisplayName("for non Cn AttributeId should return null")
- @Test
- void forNonCnAttributeIdShouldReturnNull() throws NamingException {
- Attributes attributes = new BasicAttributes();
- attributes.put("ou", "Juicy, Fruit");
-
- Assertions.assertNull(cnAttributesMapper.mapFromAttributes(attributes));
- }
-}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationRegistrationServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationRegistrationServiceIT.java
index 8ebcaa9351..4f0e3c77e4 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationRegistrationServiceIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationRegistrationServiceIT.java
@@ -6,10 +6,7 @@
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.service.persistence.UserPersistenceService;
import ch.puzzle.okr.test.SpringIntegrationTest;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
@@ -34,6 +31,8 @@ class AuthorizationRegistrationServiceIT {
private final String tenant = TestHelper.SCHEMA_PITC;
private final String key = tenant + "_" + user.getEmail();
+ private static final String EMAIL_WUNDERLAND = "wunderland@puzzle.ch";
+
@BeforeEach
void setUp() {
TenantContext.setCurrentTenant(tenant);
@@ -41,10 +40,30 @@ void setUp() {
@AfterEach
void tearDown() {
+ resetOkrChampionStatus();
+ clearCache();
+ TenantContext.setCurrentTenant(null);
+ }
+
+ private void resetOkrChampionStatus() {
+ Optional userFromDb = userPersistenceService.findByEmail(EMAIL_WUNDERLAND);
+ assertTrue(userFromDb.isPresent());
+
+ userFromDb.get().setOkrChampion(false);
+ userPersistenceService.save(userFromDb.get());
+ assertOkrChampionStatusInDb(userFromDb.get().getEmail(), false);
+ }
+
+ private void clearCache() {
Cache cache = cacheManager.getCache(AUTHORIZATION_USER_CACHE);
assertNotNull(cache);
cache.clear();
- TenantContext.setCurrentTenant(null);
+ }
+
+ private void assertOkrChampionStatusInDb(String email, boolean expectedOkrChampionStatus) {
+ var userInDb = userPersistenceService.findByEmail(email);
+ assertTrue(userInDb.isPresent());
+ assertEquals(expectedOkrChampionStatus, userInDb.get().isOkrChampion());
}
@Test
@@ -81,6 +100,8 @@ void registerAuthorizationUser_shouldSetOkrChampionsToFalse() {
// assert
assertFalse(processedUser.user().isOkrChampion());
Optional userFromDB = userPersistenceService.findByEmail(user.getEmail());
+
+ assertTrue(userFromDB.isPresent());
assertFalse(userFromDB.get().isOkrChampion());
// cleanup
@@ -89,30 +110,31 @@ void registerAuthorizationUser_shouldSetOkrChampionsToFalse() {
/*
* Special test setup.
- the user wunderland@puzzle.ch is an existing user in the H2 db (created via
- * X_TestData.sql) - the user wunderland@puzzle.ch is also defined in application-integration-test.properties as
- * user champion - with this combination we can test, that the user in the db (which has initial isOkrChampion ==
- * false) is after calling updateOrAddAuthorizationUser() a user champion. - because the user wunderland@puzzle.ch
- * exists before the test, we make no clean in db (we don't remove it)
+ * V100_0_0__TestData.sql) - the user wunderland@puzzle.ch is also defined in
+ * application-integration-test.properties as user champion - with this combination we can test, that the user in
+ * the db (which has initial isOkrChampion == false) is after calling updateOrAddAuthorizationUser() a user
+ * champion. - the OkrChampion status must manually be reset (in the tearDown method)
*/
@Test
@DisplayName("registerAuthorizationUser for a user with an email defined in the application-integration-test.properties should set OkrChampions to true")
void registerAuthorizationUserShouldSetOkrChampionsToTrue() {
// arrange
- User user = User.Builder.builder() //
- .withFirstname("Alice") //
- .withLastname("Wunderland") //
- .withEmail("wunderland@puzzle.ch") // user.champion.emails from application-integration-test.properties
- .build();
-
- userPersistenceService.getOrCreateUser(user); // updates input user with id from DB !!!
+ assertOkrChampionStatusInDb(EMAIL_WUNDERLAND, false); // pre-condition
// act
- AuthorizationUser processedUser = authorizationRegistrationService.updateOrAddAuthorizationUser(user);
+ // load user from db (by email) and set OkrChampion status based on property
+ // "okr.tenants.pitc.user.champion.emails" from application-integration-test.properties file
+ AuthorizationUser processedUser = authorizationRegistrationService
+ .updateOrAddAuthorizationUser(User.Builder.builder() //
+ .withFirstname("Alice") //
+ .withLastname("Wunderland") //
+ .withEmail(EMAIL_WUNDERLAND) // user.champion.emails from
+ // application-integration-test.properties
+ .build());
// assert
assertTrue(processedUser.user().isOkrChampion());
- Optional userFromDB = userPersistenceService.findByEmail(user.getEmail());
- assertTrue(userFromDB.get().isOkrChampion());
+ assertOkrChampionStatusInDb(processedUser.user().getEmail(), true);
}
@Test
@@ -138,6 +160,7 @@ void registerAuthorizationUser_shouldSetFirstnameAndLastnameFromToken() {
// assert
Optional userFromDB = userPersistenceService.findByEmail(user.getEmail());
+ assertTrue(userFromDB.isPresent());
assertEquals(userFromDB.get().getFirstname(), firstNameFromToken);
assertEquals(userFromDB.get().getLastname(), lastNameFromToken);
diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java
index eb4e88823f..957b27540f 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java
@@ -1,6 +1,5 @@
package ch.puzzle.okr.service.authorization;
-import ch.puzzle.okr.test.TestHelper;
import ch.puzzle.okr.dto.ErrorDto;
import ch.puzzle.okr.exception.OkrResponseStatusException;
import ch.puzzle.okr.models.Objective;
@@ -13,6 +12,7 @@
import ch.puzzle.okr.models.keyresult.KeyResultMetric;
import ch.puzzle.okr.security.JwtHelper;
import ch.puzzle.okr.service.persistence.ObjectivePersistenceService;
+import ch.puzzle.okr.test.TestHelper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -28,9 +28,9 @@
import java.util.List;
import static ch.puzzle.okr.ErrorKey.*;
-import static ch.puzzle.okr.test.TestHelper.*;
import static ch.puzzle.okr.service.authorization.AuthorizationService.hasRoleWriteAndReadAll;
import static ch.puzzle.okr.service.authorization.AuthorizationService.hasRoleWriteForTeam;
+import static ch.puzzle.okr.test.TestHelper.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/TeamAuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/TeamAuthorizationServiceTest.java
index 09243f4aac..6fb9ec4f34 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/authorization/TeamAuthorizationServiceTest.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/TeamAuthorizationServiceTest.java
@@ -130,7 +130,7 @@ void getAllTeamsShouldReturnAllTeams(boolean isWriteable) {
if (isWriteable) {
when(authorizationService.updateOrAddAuthorizationUser()).thenReturn(okrChampionUser);
} else {
- when(authorizationService.updateOrAddAuthorizationUser()).thenReturn(userWithoutWriteAllRole());
+ when(authorizationService.updateOrAddAuthorizationUser()).thenReturn(defaultAuthorizationUser());
}
when(teamBusinessService.getAllTeams(any())).thenReturn(teamList);
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaParametersTest.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaParametersTest.java
new file mode 100644
index 0000000000..7cff83afd8
--- /dev/null
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaParametersTest.java
@@ -0,0 +1,327 @@
+package ch.puzzle.okr.service.persistence;
+
+import ch.puzzle.okr.models.Objective;
+import ch.puzzle.okr.models.User;
+import jakarta.persistence.*;
+import org.apache.commons.lang3.NotImplementedException;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.*;
+import java.util.stream.Stream;
+
+import static ch.puzzle.okr.test.TestHelper.defaultAuthorizationUser;
+import static ch.puzzle.okr.test.TestHelper.mockAuthorizationUser;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class AuthorizationCriteriaParametersTest {
+
+ @DisplayName("setParameters() should be successful with default authorization user")
+ @Test
+ void setParametersShouldBeSuccessfulWithDefaultAuthorizationUser() {
+ // arrange
+ var criteria = new AuthorizationCriteria();
+ TypedQueryMock typedQueryMock = new TypedQueryMock<>();
+
+ // act
+ criteria.setParameters(typedQueryMock, defaultAuthorizationUser());
+
+ // assert
+ var expected = """
+ teamDraftState, State=DRAFT
+ userTeamIds, ListN=[1]
+ publishedStates, ListN=[ONGOING, SUCCESSFUL, NOTSUCCESSFUL]
+ """;
+
+ assertEquals(expected, typedQueryMock.getLog());
+ }
+
+ @DisplayName("setParameters() should be successful when user is okr champion")
+ @Test
+ void setParametersShouldBeSuccessfulWhenUserIsOkrChampion() {
+ // arrange
+ var user = User.Builder.builder() //
+ .withId(23L) //
+ .withFirstname("Hanna") //
+ .withLastname("muster") //
+ .withEmail("hanna.muster@example.com") //
+ .withOkrChampion(true) //
+ .build();
+ var criteria = new AuthorizationCriteria();
+ TypedQueryMock typedQueryMock = new TypedQueryMock<>();
+
+ // act
+ criteria.setParameters(typedQueryMock, mockAuthorizationUser(user));
+
+ // assert
+ var expected = """
+ allDraftState, State=DRAFT
+ publishedStates, ListN=[ONGOING, SUCCESSFUL, NOTSUCCESSFUL]
+ """;
+
+ assertEquals(expected, typedQueryMock.getLog());
+ }
+
+ @DisplayName("setParameters() should be successful when team ids or objective query are empty")
+ @ParameterizedTest
+ @MethodSource("provideListAndString")
+ void setParametersShouldBeSuccessfulWhenTeamIdsOrObjectiveQueryAreEmpty(List teamIds, String objectiveQuery) {
+ // arrange
+ var criteria = new AuthorizationCriteria();
+ TypedQueryMock typedQueryMock = new TypedQueryMock<>();
+
+ // act
+ criteria.setParameters(typedQueryMock, teamIds, objectiveQuery, defaultAuthorizationUser());
+
+ // assert
+ var expected = """
+ teamDraftState, State=DRAFT
+ userTeamIds, ListN=[1]
+ publishedStates, ListN=[ONGOING, SUCCESSFUL, NOTSUCCESSFUL]
+ """;
+
+ assertEquals(expected, typedQueryMock.getLog());
+ }
+
+ private static Stream provideListAndString() {
+ return Stream.of( //
+ Arguments.of(List.of(), null), //
+ Arguments.of(List.of(), ""), //
+ Arguments.of(null, null), //
+ Arguments.of(null, ""));
+ }
+
+ @DisplayName("setParameters() should be successful when team ids and objective query are not empty")
+ @Test
+ void setParametersShouldBeSuccessfulWhenTeamIdsAndObjectiveQueryAreNotEmpty() {
+ // arrange
+ TypedQueryMock typedQueryMock = new TypedQueryMock<>();
+ var criteria = new AuthorizationCriteria();
+ var anyTeamIds = List.of(99L);
+ var anyNonEmptyString = "OBJECTIVEQUERY";
+
+ // act
+ criteria.setParameters(typedQueryMock, anyTeamIds, anyNonEmptyString, defaultAuthorizationUser());
+
+ // assert
+ var expected = """
+ teamIds, List12=[99]
+ objectiveQuery, String=OBJECTIVEQUERY
+ teamDraftState, State=DRAFT
+ userTeamIds, ListN=[1]
+ publishedStates, ListN=[ONGOING, SUCCESSFUL, NOTSUCCESSFUL]
+ """;
+
+ assertEquals(expected, typedQueryMock.getLog());
+ }
+
+ // TypedQuery implementation for testing. The setParameterX() methods calls are logged in an internal StringBuilder
+ // which is return by getLog(). This log can be used for checking the internal state of the TypedQuery. All other
+ // methods are not implemented.
+ private static class TypedQueryMock implements TypedQuery {
+
+ private final StringBuilder log = new StringBuilder();
+
+ public String getLog() {
+ return log.toString();
+ }
+
+ @Override
+ public TypedQuery setParameter(Parameter parameter, T t) {
+ log.append(parameter.getName()).append(", ") //
+ .append(t.getClass().getSimpleName()).append("=").append(t) //
+ .append("\n");
+ return null;
+ }
+
+ @Override
+ public TypedQuery setParameter(Parameter parameter, Calendar calendar,
+ TemporalType temporalType) {
+ log.append(parameter.getName()).append(", ") //
+ .append(calendar.getTime()).append(", ") //
+ .append(temporalType.name()) //
+ .append("\n");
+ return null;
+ }
+
+ @Override
+ public TypedQuery setParameter(Parameter parameter, Date date, TemporalType temporalType) {
+ log.append(parameter.getName()).append(", ") //
+ .append(date).append(", ") //
+ .append(temporalType.name()) //
+ .append("\n");
+ return null;
+ }
+
+ @Override
+ public TypedQuery setParameter(String s, Object o) {
+ log.append(s).append(", ") //
+ .append(o.getClass().getSimpleName()).append("=").append(o) //
+ .append("\n");
+ return null;
+ }
+
+ @Override
+ public TypedQuery setParameter(String s, Calendar calendar, TemporalType temporalType) {
+ log.append(s).append(", ") //
+ .append(calendar.getTime()).append(", ") //
+ .append(temporalType.name()) //
+ .append("\n");
+ return null;
+ }
+
+ @Override
+ public TypedQuery setParameter(String s, Date date, TemporalType temporalType) {
+ log.append(s).append(", ") //
+ .append(date).append(", ") //
+ .append(temporalType.name()) //
+ .append("\n");
+ return null;
+ }
+
+ @Override
+ public TypedQuery setParameter(int i, Object o) {
+ log.append(i).append(", ") //
+ .append(o.getClass().getSimpleName()).append("=").append(o) //
+ .append("\n");
+ return null;
+ }
+
+ @Override
+ public TypedQuery setParameter(int i, Calendar calendar, TemporalType temporalType) {
+ log.append(i).append(", ") //
+ .append(calendar.getTime()).append(", ") //
+ .append(temporalType.name()) //
+ .append("\n");
+ return null;
+ }
+
+ @Override
+ public TypedQuery setParameter(int i, Date date, TemporalType temporalType) {
+ log.append(i).append(", ") //
+ .append(date).append(", ") //
+ .append(temporalType.name()) //
+ .append("\n");
+ return null;
+ }
+
+ @Override
+ public List getResultList() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Objective getSingleResult() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public int executeUpdate() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public TypedQuery setMaxResults(int i) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public int getMaxResults() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public TypedQuery setFirstResult(int i) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public int getFirstResult() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public TypedQuery setHint(String s, Object o) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Map getHints() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Set> getParameters() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Parameter> getParameter(String s) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Parameter getParameter(String s, Class aClass) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Parameter> getParameter(int i) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Parameter getParameter(int i, Class aClass) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public boolean isBound(Parameter> parameter) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public T getParameterValue(Parameter parameter) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Object getParameterValue(String s) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public Object getParameterValue(int i) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public TypedQuery setFlushMode(FlushModeType flushModeType) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public FlushModeType getFlushMode() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public TypedQuery setLockMode(LockModeType lockModeType) {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public LockModeType getLockMode() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public T unwrap(Class aClass) {
+ throw new NotImplementedException();
+ }
+ }
+
+}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaTest.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaTest.java
new file mode 100644
index 0000000000..bdb382edaf
--- /dev/null
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/AuthorizationCriteriaTest.java
@@ -0,0 +1,104 @@
+package ch.puzzle.okr.service.persistence;
+
+import ch.puzzle.okr.models.Objective;
+import ch.puzzle.okr.models.User;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import static ch.puzzle.okr.test.TestHelper.defaultAuthorizationUser;
+import static ch.puzzle.okr.test.TestHelper.mockAuthorizationUser;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+public class AuthorizationCriteriaTest {
+
+ @DisplayName("appendObjective() should be successful with default authorization user")
+ @Test
+ void appendObjectiveShouldBeSuccessfulWithDefaultAuthorizationUser() {
+ // arrange
+ var criteria = new AuthorizationCriteria();
+
+ // act
+ var current = criteria.appendObjective(defaultAuthorizationUser());
+
+ // assert
+ var expected = " and ((o.state=:teamDraftState and o.team.id IN (:userTeamIds)) or o.state IN (:publishedStates))";
+ assertEquals(expected, current);
+ }
+
+ @DisplayName("appendObjective() should be successful when user is okrChampion")
+ @Test
+ void appendObjectiveShouldBeSuccessfulWhenUserIsOkrChampion() {
+ // arrange
+ var user = User.Builder.builder() //
+ .withId(23L) //
+ .withFirstname("Hanna") //
+ .withLastname("muster") //
+ .withEmail("hanna.muster@example.com") //
+ .withOkrChampion(true) //
+ .build();
+ var criteria = new AuthorizationCriteria();
+
+ // act
+ var current = criteria.appendObjective(mockAuthorizationUser(user));
+
+ // assert
+ var expected = " and (o.state=:allDraftState or o.state IN (:publishedStates))";
+ assertEquals(expected, current);
+ }
+
+ @DisplayName("appendOverview() should be successful when team ids or objective query are empty")
+ @ParameterizedTest
+ @MethodSource("provideListAndString")
+ void appendOverviewShouldBeSuccessfulWhenTeamIdsOrObjectiveQueryAreEmpty(List teamIds,
+ String objectiveQuery) {
+ // arrange
+ var criteria = new AuthorizationCriteria();
+
+ // act
+ var current = criteria.appendOverview(teamIds, objectiveQuery, defaultAuthorizationUser());
+
+ // assert
+ var expected = "\n and ((o.objectiveState=:teamDraftState and o.overviewId.teamId IN (:userTeamIds)) or o.objectiveState IN (:publishedStates) or o.overviewId.objectiveId = -1)";
+ assertEquals(expected, current);
+ }
+
+ private static Stream provideListAndString() {
+ return Stream.of( //
+ Arguments.of(List.of(), null), //
+ Arguments.of(List.of(), ""), //
+ Arguments.of(null, null), //
+ Arguments.of(null, ""));
+ }
+
+ @DisplayName("appendOverview() should be successful when team ids and objective query are not empty")
+ @Test
+ void appendOverviewShouldBeSuccessfulWhenTeamIdsAndObjectiveQueryAreNotEmpty() {
+ // arrange
+ var criteria = new AuthorizationCriteria();
+ var anyTeamIds = List.of(99L);
+ var anyNonEmptyString = "OBJECTIVEQUERY";
+ var startingNewLine = "\n";
+ var singleSpace = " ";
+
+ // act
+ var current = criteria.appendOverview(anyTeamIds, anyNonEmptyString, defaultAuthorizationUser());
+
+ // assert
+ var expected = startingNewLine + singleSpace
+ + """
+ and o.overviewId.teamId in (:teamIds)
+ and lower(coalesce(o.objectiveTitle, '')) like lower(concat('%',:objectiveQuery,'%'))
+ and ((o.objectiveState=:teamDraftState and o.overviewId.teamId IN (:userTeamIds)) or o.objectiveState IN (:publishedStates) or o.overviewId.objectiveId = -1)""";
+
+ assertEquals(expected, current);
+ assertFalse(current.contains(anyNonEmptyString));
+ }
+
+}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/CheckInPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/CheckInPersistenceServiceIT.java
index 419971e392..0837b5f996 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/persistence/CheckInPersistenceServiceIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/CheckInPersistenceServiceIT.java
@@ -1,52 +1,32 @@
package ch.puzzle.okr.service.persistence;
-import ch.puzzle.okr.test.TestHelper;
-import ch.puzzle.okr.dto.ErrorDto;
-import ch.puzzle.okr.exception.OkrResponseStatusException;
-import ch.puzzle.okr.models.Objective;
-import ch.puzzle.okr.models.User;
import ch.puzzle.okr.models.checkin.CheckIn;
-import ch.puzzle.okr.models.checkin.CheckInMetric;
-import ch.puzzle.okr.models.keyresult.KeyResultMetric;
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.test.SpringIntegrationTest;
+import ch.puzzle.okr.test.TestHelper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.server.ResponseStatusException;
-import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
+import static ch.puzzle.okr.Constants.CHECK_IN;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringIntegrationTest
class CheckInPersistenceServiceIT {
- CheckIn createdCheckIn;
+
+ private static final long KEY_RESULT_ID = 7L;
@Autowired
private CheckInPersistenceService checkInPersistenceService;
- private static CheckIn createCheckIn(Long id) {
- return createCheckIn(id, 1);
- }
-
- private static final String UPDATED_CHECKIN = "Updated CheckIn";
-
- private static CheckIn createCheckIn(Long id, int version) {
- return CheckInMetric.Builder.builder().withValue(30D).withId(id).withVersion(version)
- .withCreatedBy(User.Builder.builder().withId(1L).withFirstname("Frank").build())
- .withCreatedOn(LocalDateTime.MAX)
- .withKeyResult(KeyResultMetric.Builder.builder().withBaseline(1.0).withStretchGoal(13.0).withId(8L)
- .withObjective(Objective.Builder.builder().withId(1L).build()).build())
- .withChangeInfo("ChangeInfo").withInitiatives("Initiatives").withModifiedOn(LocalDateTime.MAX)
- .withConfidence(5).build();
- }
-
@BeforeEach
void setUp() {
TenantContext.setCurrentTenant(TestHelper.SCHEMA_PITC);
@@ -54,97 +34,53 @@ void setUp() {
@AfterEach
void tearDown() {
- try {
- if (createdCheckIn != null) {
- checkInPersistenceService.findById(createdCheckIn.getId());
- checkInPersistenceService.deleteById(createdCheckIn.getId());
- }
- } catch (ResponseStatusException ex) {
- // created CheckIn already deleted
- } finally {
- createdCheckIn = null;
- }
TenantContext.setCurrentTenant(null);
}
+ // uses data from V100_0_0__TestData.sql
+ @DisplayName("getCheckInsByKeyResultIdOrderByCheckInDate() should get checkIns by keyResultId and order them by date desc")
@Test
- void saveCheckInShouldSaveNewCheckIn() {
- CheckIn checkIn = createCheckIn(null);
-
- createdCheckIn = checkInPersistenceService.save(checkIn);
-
- assertNotNull(createdCheckIn.getId());
- assertEquals(checkIn.getModifiedOn(), createdCheckIn.getModifiedOn());
- assertEquals(((CheckInMetric) checkIn).getValue(), ((CheckInMetric) createdCheckIn).getValue());
- assertEquals(checkIn.getCreatedBy(), createdCheckIn.getCreatedBy());
- assertEquals(checkIn.getCreatedOn(), createdCheckIn.getCreatedOn());
- assertEquals(checkIn.getInitiatives(), createdCheckIn.getInitiatives());
- assertEquals(checkIn.getChangeInfo(), createdCheckIn.getChangeInfo());
+ void getCheckInsByKeyResultIdOrderByCheckInDateShouldGetCheckInsByKeyResultIdAndOrderThemByDateDesc() {
+ // act
+ List checkIns = checkInPersistenceService
+ .getCheckInsByKeyResultIdOrderByCheckInDateDesc(KEY_RESULT_ID);
+
+ // assert
+ assertThat(2, greaterThanOrEqualTo(checkIns.size()));
+ CheckIn firstCheckIn = checkIns.get(0);
+ CheckIn lastCheckIn = checkIns.get(checkIns.size() - 1);
+ assertFirstIsCreatedAfterSecond(firstCheckIn, lastCheckIn);
}
- @Test
- void updateKeyResultShouldUpdateKeyResult() {
- createdCheckIn = checkInPersistenceService.save(createCheckIn(null));
- CheckIn updateCheckIn = createCheckIn(createdCheckIn.getId(), createdCheckIn.getVersion());
- updateCheckIn.setChangeInfo(UPDATED_CHECKIN);
-
- CheckIn updatedCheckIn = checkInPersistenceService.save(updateCheckIn);
-
- assertEquals(createdCheckIn.getId(), updatedCheckIn.getId());
- assertEquals(createdCheckIn.getVersion() + 1, updatedCheckIn.getVersion());
- assertEquals(UPDATED_CHECKIN, updatedCheckIn.getChangeInfo());
+ private void assertFirstIsCreatedAfterSecond(CheckIn first, CheckIn second) {
+ assertTrue(first.getCreatedOn().isAfter(second.getCreatedOn()));
}
+ // uses data from V100_0_0__TestData.sql
+ @DisplayName("getLastCheckInOfKeyResult() should get last checkIn of keyResult")
@Test
- void updateKeyResultShouldThrowExceptionWhenAlreadyUpdated() {
- createdCheckIn = checkInPersistenceService.save(createCheckIn(null));
- CheckIn updateCheckIn = createCheckIn(createdCheckIn.getId(), 0);
- updateCheckIn.setChangeInfo(UPDATED_CHECKIN);
+ void getLastCheckInOfKeyResultShouldGetLastCheckInOfKeyResult() {
+ // act
+ var lastCheckIn = checkInPersistenceService.getLastCheckInOfKeyResult(KEY_RESULT_ID);
- OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class,
- () -> checkInPersistenceService.save(updateCheckIn));
-
- List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of("Check-in")));
-
- assertEquals(UNPROCESSABLE_ENTITY, exception.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(exception.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason()));
+ // assert
+ var allCheckins = checkInPersistenceService.getCheckInsByKeyResultIdOrderByCheckInDateDesc(KEY_RESULT_ID);
+ assertLastIsCreatedAfterAllOtherCheckIns(lastCheckIn, allCheckins);
}
- @Test
- void getAllCheckInShouldReturnListOfAllCheckIns() {
- List checkIns = checkInPersistenceService.findAll();
-
- assertEquals(19, checkIns.size());
- }
-
- @Test
- void getCheckInByIdShouldReturnCheckInProperly() {
- CheckIn checkIn = checkInPersistenceService.findById(20L);
-
- assertEquals(20L, checkIn.getId());
- assertEquals(0.5, ((CheckInMetric) checkIn).getValue(), 0.01);
- assertEquals(
- "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore "
- + "magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores ",
- checkIn.getChangeInfo());
+ private void assertLastIsCreatedAfterAllOtherCheckIns(CheckIn last, List allCheckIns) {
+ for (CheckIn checkInLoop : allCheckIns) {
+ if (!Objects.equals(checkInLoop.getId(), last.getId())) {
+ assertTrue(last.getCreatedOn().isAfter(checkInLoop.getCreatedOn()));
+ }
+ }
}
+ @DisplayName("getModelName() should return checkIn")
@Test
- void shouldGetCheckInsByKeyResultIdAndOrderThemByDateDesc() {
- List checkIns = checkInPersistenceService.getCheckInsByKeyResultIdOrderByCheckInDateDesc(7L);
- assertTrue(checkIns.get(0).getCreatedOn().isAfter(checkIns.get(checkIns.size() - 1).getCreatedOn()));
+ void getModelNameShouldReturnCheckIn() {
+ assertEquals(CHECK_IN, checkInPersistenceService.getModelName());
}
- @Test
- void shouldGetLastCheckInOfKeyResult() {
- CheckIn checkIn = checkInPersistenceService.getLastCheckInOfKeyResult(7L);
- List checkInList = checkInPersistenceService.getCheckInsByKeyResultIdOrderByCheckInDateDesc(7L);
- for (CheckIn checkInLoop : checkInList) {
- if (!Objects.equals(checkInLoop.getId(), checkIn.getId())) {
- assertTrue(checkIn.getCreatedOn().isAfter(checkInLoop.getCreatedOn()));
- }
- }
- }
}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java
index ccc6c3a8a0..9708e9d779 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java
@@ -2,56 +2,68 @@
import ch.puzzle.okr.dto.ErrorDto;
import ch.puzzle.okr.exception.OkrResponseStatusException;
-import ch.puzzle.okr.models.*;
+import ch.puzzle.okr.models.Objective;
+import ch.puzzle.okr.models.Quarter;
+import ch.puzzle.okr.models.Team;
import ch.puzzle.okr.models.authorization.AuthorizationUser;
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.test.SpringIntegrationTest;
import ch.puzzle.okr.test.TestHelper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.server.ResponseStatusException;
+import org.springframework.http.HttpStatus;
-import java.time.LocalDateTime;
import java.util.List;
+import java.util.stream.Stream;
-import static ch.puzzle.okr.test.TestConstants.GJ_FOR_TESTS_QUARTER_ID;
+import static ch.puzzle.okr.exception.OkrResponseStatusException.of;
import static ch.puzzle.okr.test.TestHelper.defaultAuthorizationUser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
-import static org.springframework.http.HttpStatus.*;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+import static org.springframework.http.HttpStatus.UNAUTHORIZED;
+// tests are using data from V100_0_0__TestData.sql
@SpringIntegrationTest
class ObjectivePersistenceServiceIT {
- private static final String REASON = "not authorized to read objective";
- private static final OkrResponseStatusException exception = OkrResponseStatusException.of(REASON);
- private static final String HIGHER_CUSTOMER_HAPPINESS = "Wir wollen die Kundenzufriedenheit steigern";
- private static final String MODEL_WITH_ID_NOT_FOUND = "MODEL_WITH_ID_NOT_FOUND";
+ private static final long INVALID_OBJECTIVE_ID = 321L;
+ private static final long INVALID_KEY_RESULT_ID = 321L;
+ private static final long INVALID_CHECK_IN_ID = 321L;
+ private static final long INVALID_TEAM_ID = 321L;
+ private static final long INVALID_QUARTER_ID = 12L;
+
+ private static final long ID_OF_OBJECTIVE_3 = 3L;
+ private static final long ID_OF_OBJECTIVE_8 = 8L;
+ private static final long ID_OF_OBJECTIVE_9 = 9L;
+ private static final long ID_OF_OBJECTIVE_10 = 10L;
+
+ private static final String TITLE_OF_OBJECTIVE_3 = "Wir wollen die Kundenzufriedenheit steigern";
+ private static final String TITLE_OF_OBJECTIVE_8 = "consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua";
+ private static final String TITLE_OF_OBJECTIVE_9 = "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.";
+ private static final String TITLE_OF_OBJECTIVE_10 = "should not appear on staging, no sea takimata sanctus est Lorem ipsum dolor sit amet.";
+
+ private static final long ID_OF_KEY_RESULT_5 = 5L;
+ private static final long ID_OF_CHECK_IN_7 = 7L;
+ private static final long ID_OF_TEAM_6 = 6L;
+
+ private static final String REASON_UNAUTHORIZED = "not authorized to read objective";
+ private static final OkrResponseStatusException NO_RESULT_EXCEPTION = of(REASON_UNAUTHORIZED);
+
private static final String OBJECTIVE = "Objective";
private static final String ATTRIBUTE_NULL = "ATTRIBUTE_NULL";
+ private static final long CURRENT_QUARTER_ID = 2L;
+
private final AuthorizationUser authorizationUser = defaultAuthorizationUser();
- private Objective createdObjective;
@Autowired
private ObjectivePersistenceService objectivePersistenceService;
- @Autowired
- private TeamPersistenceService teamPersistenceService;
- @Autowired
- private QuarterPersistenceService quarterPersistenceService;
-
- private static Objective createObjective(Long id) {
- return createObjective(id, 1);
- }
-
- private static Objective createObjective(Long id, int version) {
- return Objective.Builder.builder().withId(id).withVersion(version).withTitle("title")
- .withCreatedBy(User.Builder.builder().withId(1L).build())
- .withTeam(Team.Builder.builder().withId(5L).build())
- .withQuarter(Quarter.Builder.builder().withId(GJ_FOR_TESTS_QUARTER_ID).build())
- .withDescription("This is our description").withState(State.DRAFT).withCreatedOn(LocalDateTime.MAX)
- .withModifiedOn(LocalDateTime.MAX).withModifiedBy(User.Builder.builder().withId(1L).build()).build();
- }
@BeforeEach
void setUp() {
@@ -60,206 +72,209 @@ void setUp() {
@AfterEach
void tearDown() {
- try {
- if (createdObjective != null) {
- objectivePersistenceService.findById(createdObjective.getId());
- objectivePersistenceService.deleteById(createdObjective.getId());
- }
- } catch (ResponseStatusException ex) {
- // created key result already deleted
- } finally {
- createdObjective = null;
- }
TenantContext.setCurrentTenant(null);
}
- @Test
- void findAllShouldReturnListOfObjectives() {
- List objectives = objectivePersistenceService.findAll();
-
- assertEquals(7, objectives.size());
- }
-
+ @DisplayName("findObjectiveById() should return objective properly")
@Test
void findObjectiveByIdShouldReturnObjectiveProperly() {
- Objective objective = objectivePersistenceService.findObjectiveById(3L, authorizationUser, exception);
+ // act
+ var objective = objectivePersistenceService.findObjectiveById(ID_OF_OBJECTIVE_3, authorizationUser,
+ NO_RESULT_EXCEPTION);
- assertEquals(3L, objective.getId());
- assertEquals(HIGHER_CUSTOMER_HAPPINESS, objective.getTitle());
+ // assert
+ assertObjective(ID_OF_OBJECTIVE_3, TITLE_OF_OBJECTIVE_3, objective);
}
+ @DisplayName("findObjectiveById() should throw exception when objective not found")
@Test
void findObjectiveByIdShouldThrowExceptionWhenObjectiveNotFound() {
- ResponseStatusException findObjectiveException = assertThrows(OkrResponseStatusException.class,
- () -> objectivePersistenceService.findObjectiveById(321L, authorizationUser,
- ObjectivePersistenceServiceIT.exception));
+ // act
+ var exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService
+ .findObjectiveById(INVALID_OBJECTIVE_ID, authorizationUser, NO_RESULT_EXCEPTION));
- assertEquals(UNAUTHORIZED, findObjectiveException.getStatusCode());
- assertEquals(REASON, findObjectiveException.getReason());
+ // assert
+ var expectedErrors = List.of(new ErrorDto(REASON_UNAUTHORIZED, List.of()));
+ assertResponseStatusException(UNAUTHORIZED, expectedErrors, exception);
}
+ @DisplayName("findObjectiveById() should throw exception when objective id is null")
@Test
void findObjectiveByIdShouldThrowExceptionWhenObjectiveIdIsNull() {
- OkrResponseStatusException findObjectiveException = assertThrows(OkrResponseStatusException.class,
- () -> objectivePersistenceService.findObjectiveById(null, authorizationUser,
- ObjectivePersistenceServiceIT.exception));
-
- List expectedErrors = List.of(new ErrorDto(ATTRIBUTE_NULL, List.of("ID", OBJECTIVE)));
+ // act
+ var exception = assertThrows(OkrResponseStatusException.class,
+ () -> objectivePersistenceService.findObjectiveById(null, authorizationUser, NO_RESULT_EXCEPTION));
- assertEquals(BAD_REQUEST, findObjectiveException.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(findObjectiveException.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(findObjectiveException.getReason()));
+ // assert
+ var expectedErrors = List.of(new ErrorDto(ATTRIBUTE_NULL, List.of("ID", OBJECTIVE)));
+ assertResponseStatusException(BAD_REQUEST, expectedErrors, exception);
}
+ @DisplayName("findObjectiveByKeyResultId() should return objective properly")
@Test
void findObjectiveByKeyResultIdShouldReturnObjectiveProperly() {
- Objective objective = objectivePersistenceService.findObjectiveByKeyResultId(5L, authorizationUser, exception);
+ // act
+ var objective = objectivePersistenceService.findObjectiveByKeyResultId(ID_OF_KEY_RESULT_5, authorizationUser,
+ NO_RESULT_EXCEPTION);
- assertEquals(3L, objective.getId());
- assertEquals(HIGHER_CUSTOMER_HAPPINESS, objective.getTitle());
+ // assert
+ assertObjective(ID_OF_OBJECTIVE_3, TITLE_OF_OBJECTIVE_3, objective);
}
+ @DisplayName("findObjectiveByKeyResultId() should throw exception when objective not found")
@Test
void findObjectiveByKeyResultIdShouldThrowExceptionWhenObjectiveNotFound() {
- ResponseStatusException objectiveByKeyResultException = assertThrows(ResponseStatusException.class,
- () -> objectivePersistenceService.findObjectiveByKeyResultId(321L, authorizationUser,
- ObjectivePersistenceServiceIT.exception));
+ // act
+ var exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService
+ .findObjectiveByKeyResultId(INVALID_KEY_RESULT_ID, authorizationUser, NO_RESULT_EXCEPTION));
- assertEquals(UNAUTHORIZED, objectiveByKeyResultException.getStatusCode());
- assertEquals(REASON, objectiveByKeyResultException.getReason());
+ // assert
+ var expectedErrors = List.of(new ErrorDto(REASON_UNAUTHORIZED, List.of()));
+ assertResponseStatusException(UNAUTHORIZED, expectedErrors, exception);
}
+ @DisplayName("findObjectiveByKeyResultId() should throw exception when objective id is null")
@Test
void findObjectiveByKeyResultIdShouldThrowExceptionWhenObjectiveIdIsNull() {
- OkrResponseStatusException objectiveByKeyResultException = assertThrows(OkrResponseStatusException.class,
- () -> objectivePersistenceService.findObjectiveByKeyResultId(null, authorizationUser,
- ObjectivePersistenceServiceIT.exception));
+ // act
+ var exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService
+ .findObjectiveByKeyResultId(null, authorizationUser, NO_RESULT_EXCEPTION));
- List expectedErrors = List.of(new ErrorDto(ATTRIBUTE_NULL, List.of("ID", OBJECTIVE)));
-
- assertEquals(BAD_REQUEST, objectiveByKeyResultException.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(objectiveByKeyResultException.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(objectiveByKeyResultException.getReason()));
+ // assert
+ var expectedErrors = List.of(new ErrorDto(ATTRIBUTE_NULL, List.of("ID", OBJECTIVE)));
+ assertResponseStatusException(BAD_REQUEST, expectedErrors, exception);
}
+ @DisplayName("findObjectiveByCheckInId() should return objective properly")
@Test
void findObjectiveByCheckInIdShouldReturnObjectiveProperly() {
- Objective objective = objectivePersistenceService.findObjectiveByCheckInId(7L, authorizationUser, exception);
+ // act
+ var objective = objectivePersistenceService.findObjectiveByCheckInId(ID_OF_CHECK_IN_7, authorizationUser,
+ NO_RESULT_EXCEPTION);
- assertEquals(3L, objective.getId());
- assertEquals(HIGHER_CUSTOMER_HAPPINESS, objective.getTitle());
+ // assert
+ assertObjective(ID_OF_OBJECTIVE_3, TITLE_OF_OBJECTIVE_3, objective);
}
+ @DisplayName("findObjectiveByCheckInId() should throw exception when objective not found")
@Test
void findObjectiveByCheckInIdShouldThrowExceptionWhenObjectiveNotFound() {
- ResponseStatusException objectiveByCheckInException = assertThrows(ResponseStatusException.class,
- () -> objectivePersistenceService.findObjectiveByCheckInId(321L, authorizationUser,
- ObjectivePersistenceServiceIT.exception));
+ // act
+ var exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService
+ .findObjectiveByCheckInId(INVALID_CHECK_IN_ID, authorizationUser, NO_RESULT_EXCEPTION));
- assertEquals(UNAUTHORIZED, objectiveByCheckInException.getStatusCode());
- assertEquals(REASON, objectiveByCheckInException.getReason());
+ // assert
+ var expectedErrors = List.of(new ErrorDto(REASON_UNAUTHORIZED, List.of()));
+ assertResponseStatusException(UNAUTHORIZED, expectedErrors, exception);
}
+ @DisplayName("findObjectiveByCheckInId() should throw exception when objective id is null")
@Test
void findObjectiveByCheckInIdShouldThrowExceptionWhenObjectiveIdIsNull() {
- OkrResponseStatusException objectiveByCheckInException = assertThrows(OkrResponseStatusException.class,
- () -> objectivePersistenceService.findObjectiveByCheckInId(null, authorizationUser,
- ObjectivePersistenceServiceIT.exception));
-
- List expectedErrors = List.of(new ErrorDto(ATTRIBUTE_NULL, List.of("ID", OBJECTIVE)));
+ // act
+ var exception = assertThrows(OkrResponseStatusException.class, () -> objectivePersistenceService
+ .findObjectiveByCheckInId(null, authorizationUser, ObjectivePersistenceServiceIT.NO_RESULT_EXCEPTION));
- assertEquals(BAD_REQUEST, objectiveByCheckInException.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(objectiveByCheckInException.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(objectiveByCheckInException.getReason()));
+ // assert
+ var expectedErrors = List.of(new ErrorDto(ATTRIBUTE_NULL, List.of("ID", OBJECTIVE)));
+ assertResponseStatusException(BAD_REQUEST, expectedErrors, exception);
}
+ @DisplayName("findObjectiveByTeamId() should return objectives of team properly")
@Test
- void saveObjectiveShouldSaveNewObjective() {
- Objective objective = createObjective(null);
-
- createdObjective = objectivePersistenceService.save(objective);
-
- assertNotNull(createdObjective.getId());
- assertEquals(objective.getDescription(), createdObjective.getDescription());
- assertEquals(objective.getDescription(), createdObjective.getDescription());
- assertEquals(objective.getModifiedOn(), createdObjective.getModifiedOn());
+ void findObjectiveByTeamIdShouldReturnObjectivesOfTeamProperly() {
+ // act
+ var objectives = objectivePersistenceService.findObjectiveByTeamId(ID_OF_TEAM_6);
+
+ // assert
+ assertEquals(3, objectives.size());
+ assertObjective(ID_OF_OBJECTIVE_8, TITLE_OF_OBJECTIVE_8, objectives.get(0));
+ assertObjective(ID_OF_OBJECTIVE_9, TITLE_OF_OBJECTIVE_9, objectives.get(1));
+ assertObjective(ID_OF_OBJECTIVE_10, TITLE_OF_OBJECTIVE_10, objectives.get(2));
}
+ @DisplayName("findObjectiveByTeamId() should return empty list when objective not found")
@Test
- void updateObjectiveShouldUpdateObjective() {
- createdObjective = objectivePersistenceService.save(createObjective(null));
- Objective updateObjective = createObjective(createdObjective.getId(), createdObjective.getVersion());
- updateObjective.setState(State.ONGOING);
-
- Objective updatedObjective = objectivePersistenceService.save(updateObjective);
+ void findObjectiveByTeamIdShouldReturnEmptyListWhenObjectiveNotFound() {
+ // act
+ var objectives = objectivePersistenceService.findObjectiveByTeamId(INVALID_TEAM_ID);
- assertEquals(createdObjective.getId(), updatedObjective.getId());
- assertEquals(State.ONGOING, updatedObjective.getState());
+ // assert
+ assertTrue(objectives.isEmpty());
}
+ @DisplayName("findObjectiveByTeamId() should return empty list when objective id is null")
@Test
- void updateObjectiveShouldThrowExceptionWhenAlreadyUpdated() {
- createdObjective = objectivePersistenceService.save(createObjective(null));
- Objective updateObjective = createObjective(createdObjective.getId(), 0);
- updateObjective.setState(State.ONGOING);
-
- OkrResponseStatusException objectiveSaveException = assertThrows(OkrResponseStatusException.class,
- () -> objectivePersistenceService.save(updateObjective));
- List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of(OBJECTIVE)));
-
- assertEquals(UNPROCESSABLE_ENTITY, objectiveSaveException.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(objectiveSaveException.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(objectiveSaveException.getReason()));
- }
-
- @Test
- void deleteObjectiveShouldThrowExceptionWhenKeyResultNotFound() {
- Objective objective = createObjective(321L);
- createdObjective = objectivePersistenceService.save(objective);
- objectivePersistenceService.deleteById(createdObjective.getId());
-
- Long objectiveId = createdObjective.getId();
- OkrResponseStatusException findObjectiveException = assertThrows(OkrResponseStatusException.class,
- () -> objectivePersistenceService.findById(objectiveId));
+ void findObjectiveByTeamIdShouldReturnEmptyListWhenObjectiveIdIsNull() {
+ // act
+ var objectives = objectivePersistenceService.findObjectiveByTeamId(null);
- List expectedErrors = List.of(new ErrorDto(MODEL_WITH_ID_NOT_FOUND, List.of(OBJECTIVE, "200")));
-
- assertEquals(NOT_FOUND, findObjectiveException.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(findObjectiveException.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(findObjectiveException.getReason()));
+ // assert
+ assertTrue(objectives.isEmpty());
}
+ @DisplayName("countByTeamAndQuarter() should return number of objectives for current quarter")
@Test
- void countByTeamAndQuarterShouldThrowErrorIfQuarterDoesNotExist() {
- Team teamId5 = teamPersistenceService.findById(5L);
- OkrResponseStatusException countByTeamException = assertThrows(OkrResponseStatusException.class,
- () -> objectivePersistenceService.countByTeamAndQuarter(teamId5,
- quarterPersistenceService.findById(12L)));
-
- List expectedErrors = List.of(new ErrorDto(MODEL_WITH_ID_NOT_FOUND, List.of("Quarter", "12")));
+ void countByTeamAndQuarterShouldReturnNumberOfObjectivesForCurrentQuarter() {
+ // arrange: there are 3 objectives for the current quarter (id 2) for team with id 6
+ var team = Team.Builder.builder().withId(ID_OF_TEAM_6).build();
+ var quarter = Quarter.Builder.builder().withId(CURRENT_QUARTER_ID).build();
- assertEquals(NOT_FOUND, countByTeamException.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(countByTeamException.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(countByTeamException.getReason()));
+ // act
+ var count = objectivePersistenceService.countByTeamAndQuarter(team, quarter);
- Quarter quarterId2 = quarterPersistenceService.findById(2L);
- OkrResponseStatusException exceptionTeam = assertThrows(OkrResponseStatusException.class,
- () -> objectivePersistenceService.countByTeamAndQuarter(teamPersistenceService.findById(500L),
- quarterId2));
+ // assert
+ assertEquals(3, count);
+ }
- List expectedErrorsTeam = List.of(new ErrorDto(MODEL_WITH_ID_NOT_FOUND, List.of("Team", "500")));
+ @DisplayName("countByTeamAndQuarter() should return zero when team or quarter is not valid or null")
+ @ParameterizedTest
+ @MethodSource("invalidTeamsAndQuarters")
+ void countByTeamAndQuarterShouldReturnZeroWhenTeamOrQuarterIsNotValidOrNull(Team team, Quarter quarter) {
+ // act
+ var count = objectivePersistenceService.countByTeamAndQuarter(team, quarter);
- assertEquals(NOT_FOUND, exceptionTeam.getStatusCode());
- assertThat(expectedErrorsTeam).hasSameElementsAs(exceptionTeam.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrorsTeam).contains(exceptionTeam.getReason()));
+ // assert
+ assertEquals(0, count);
+ }
+ private static Stream invalidTeamsAndQuarters() {
+ var validTeam = Team.Builder.builder().withId(ID_OF_TEAM_6).build();
+ var invalidTeam = Team.Builder.builder().withId(INVALID_TEAM_ID).build();
+ var validQuarter = Quarter.Builder.builder().withId(CURRENT_QUARTER_ID).build();
+ var invalidQuarter = Quarter.Builder.builder().withId(INVALID_QUARTER_ID).build();
+
+ return Stream.of(
+ // valid team + invalid quarter
+ arguments(validTeam, invalidQuarter),
+ // valid team + null quarter
+ arguments(validTeam, null),
+ // invalid team + valid quarter
+ arguments(invalidTeam, validQuarter),
+ // invalid team + null quarter
+ arguments(null, validQuarter),
+ // invalid team + invalid quarter
+ arguments(invalidTeam, invalidQuarter),
+ // null team + null quarter
+ arguments(null, null));
}
+ @DisplayName("getModelName() should return Objective")
@Test
- void countByTeamAndQuarterShouldReturnCountValue() {
- Integer count = objectivePersistenceService.countByTeamAndQuarter(Team.Builder.builder().withId(5L).build(),
- Quarter.Builder.builder().withId(2L).build());
+ void getModelNameShouldReturnObjective() {
+ assertEquals(OBJECTIVE, objectivePersistenceService.getModelName());
+ }
+
+ private void assertResponseStatusException(HttpStatus expectedStatus, List expectedErrors,
+ OkrResponseStatusException currentException) {
+ assertEquals(expectedStatus, currentException.getStatusCode());
+ assertThat(expectedErrors).hasSameElementsAs(currentException.getErrors());
+ assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(currentException.getReason()));
+ }
- assertEquals(2, count);
+ private void assertObjective(Long expectedId, String expectedTitle, Objective currentObjective) {
+ assertEquals(expectedId, currentObjective.getId());
+ assertEquals(expectedTitle, currentObjective.getTitle());
}
+
}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/PersistenceBaseTestIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/PersistenceBaseTestIT.java
new file mode 100644
index 0000000000..7f5a60a5e5
--- /dev/null
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/PersistenceBaseTestIT.java
@@ -0,0 +1,182 @@
+package ch.puzzle.okr.service.persistence;
+
+import ch.puzzle.okr.dto.ErrorDto;
+import ch.puzzle.okr.models.User;
+import ch.puzzle.okr.multitenancy.TenantContext;
+import ch.puzzle.okr.repository.UserRepository;
+import ch.puzzle.okr.test.SpringIntegrationTest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.OptimisticLockingFailureException;
+import org.springframework.web.server.ResponseStatusException;
+
+import java.util.List;
+
+import static ch.puzzle.okr.test.TestHelper.getAllErrorKeys;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.springframework.http.HttpStatus.*;
+
+/**
+ * Testing the functionality of the abstract PersistenceBase and use UserRepository as example of a CrudRepository
+ * implementation.
+ *
+ * Tests depending on data from V100_0_0__TestData.sql
+ */
+@SpringIntegrationTest
+public class PersistenceBaseTestIT {
+
+ private User createdUser;
+
+ private static final long NON_EXISTING_USER_ID = 321L;
+ private static final long USER_PACO_ID = 1L;
+ private static final User USER_WITHOUT_CONSTRAINTS = User.Builder.builder() //
+ .withFirstname("Hans") //
+ .withLastname("Muster") //
+ .withEmail("hans.muster@puzzle.ch") //
+ .build();
+
+ @Autowired
+ private PersistenceBase persistenceBase;
+
+ @BeforeEach
+ void setUp() {
+ TenantContext.setCurrentTenant("pitc");
+ }
+
+ @AfterEach
+ void tearDown() {
+ if (createdUser != null) {
+ persistenceBase.deleteById(createdUser.getId());
+ createdUser = null;
+ }
+ TenantContext.setCurrentTenant(null);
+ }
+
+ @DisplayName("findById() should return single entity if entity with id exists")
+ @Test
+ void findByIdShouldReturnSingleEntityIfEntityWithIdExists() {
+ var foundUser = persistenceBase.findById(USER_PACO_ID);
+
+ assertEquals(USER_PACO_ID, foundUser.getId());
+ assertUser("Paco", "Eggimann", "peggimann@puzzle.ch", foundUser);
+ }
+
+ @DisplayName("findById() should throw exception if entity with id does not exist")
+ @Test
+ void findByIdShouldThrowExceptionIfEntityWithIdDoesNotExist() {
+ var exception = assertThrows(ResponseStatusException.class,
+ () -> persistenceBase.findById(NON_EXISTING_USER_ID));
+
+ assertEquals(NOT_FOUND, exception.getStatusCode());
+ assertErrorKey("MODEL_WITH_ID_NOT_FOUND", exception);
+ }
+
+ @DisplayName("findById() should throw exception if id is null")
+ @Test
+ void findByIdShouldThrowExceptionIfIdIsNull() {
+ var exception = assertThrows(ResponseStatusException.class, () -> persistenceBase.findById(null));
+
+ assertEquals(BAD_REQUEST, exception.getStatusCode());
+ assertErrorKey("ATTRIBUTE_NULL", exception);
+ }
+
+ @DisplayName("findAll() should return all entities as list")
+ @Test
+ void findAllShouldReturnAllEntitiesAsList() throws ResponseStatusException {
+ var userList = persistenceBase.findAll();
+
+ assertThat(userList.size()).isGreaterThanOrEqualTo(7);
+ }
+
+ @DisplayName("save() should add new entity")
+ @Test
+ void saveShouldAddNewEntity() throws ResponseStatusException {
+ createdUser = persistenceBase.save(USER_WITHOUT_CONSTRAINTS);
+
+ assertNotNull(createdUser);
+ assertUser("Hans", "Muster", "hans.muster@puzzle.ch", createdUser);
+ }
+
+ @DisplayName("save() should throw exception in the case of optimistic locking failure")
+ @Test
+ void saveShouldThrowExceptionInTheCaseOfOptimisticLockingFailure() throws ResponseStatusException {
+ // arrange
+ var testRepository = mock(UserRepository.class);
+ when(testRepository.save(any())).thenThrow(OptimisticLockingFailureException.class);
+
+ var persistenceBaseForTest = new PersistenceBase<>(testRepository) {
+ @Override
+ public String getModelName() {
+ return "for_test";
+ }
+ };
+
+ // act + assert
+ var exception = assertThrows(ResponseStatusException.class, () -> persistenceBaseForTest.save(createdUser));
+
+ // assert
+ assertEquals(UNPROCESSABLE_ENTITY, exception.getStatusCode());
+ assertErrorKey("DATA_HAS_BEEN_UPDATED", exception);
+ }
+
+ @DisplayName("save() existing entity with different data should update existing entity")
+ @Test
+ void saveExistingEntityWithDifferentDataShouldUpdateExistingEntity() throws ResponseStatusException {
+ // arrange
+ createdUser = persistenceBase.save(USER_WITHOUT_CONSTRAINTS);
+ var createdUserId = createdUser.getId();
+ var foundUser = persistenceBase.findById(createdUserId);
+
+ // pro-condition
+ assertEquals("Hans", createdUser.getFirstname());
+
+ // act
+ foundUser.setFirstname("Pekka");
+ persistenceBase.save(foundUser);
+ foundUser = persistenceBase.findById(createdUserId);
+
+ // assert
+ assertEquals(createdUserId, foundUser.getId());
+ assertEquals("Pekka", foundUser.getFirstname());
+ }
+
+ @DisplayName("deleteById() should delete entity")
+ @Test
+ void deleteByIdShouldDeleteEntity() throws ResponseStatusException {
+ // arrange
+ createdUser = persistenceBase.save(USER_WITHOUT_CONSTRAINTS);
+ var createdUserId = createdUser.getId();
+ assertNotNull(persistenceBase.findById(createdUserId));
+
+ // act
+ persistenceBase.deleteById(createdUserId);
+
+ // assert
+ assertEntityNotFound(createdUserId);
+ }
+
+ private static void assertUser(String expectedFirstName, String expectedLastName, String expectedEmail,
+ User currentUser) {
+ assertEquals(expectedFirstName, currentUser.getFirstname());
+ assertEquals(expectedLastName, currentUser.getLastname());
+ assertEquals(expectedEmail, currentUser.getEmail());
+ }
+
+ private void assertErrorKey(String errorKey, ResponseStatusException exception) {
+ var errorKeys = getAllErrorKeys(List.of(new ErrorDto(errorKey, List.of("User"))));
+ assertTrue(errorKeys.contains(exception.getReason()));
+ }
+
+ private void assertEntityNotFound(long entityId) {
+ var exception = assertThrows(ResponseStatusException.class, () -> persistenceBase.findById(entityId));
+ assertEquals(NOT_FOUND, exception.getStatusCode());
+ assertErrorKey("MODEL_WITH_ID_NOT_FOUND", exception);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java
index 23a1ce5faf..1eee877cc1 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/QuarterPersistenceServiceIT.java
@@ -1,7 +1,5 @@
package ch.puzzle.okr.service.persistence;
-import ch.puzzle.okr.dto.ErrorDto;
-import ch.puzzle.okr.exception.OkrResponseStatusException;
import ch.puzzle.okr.models.Quarter;
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.test.SpringIntegrationTest;
@@ -16,12 +14,10 @@
import java.time.LocalDate;
import java.util.List;
+import static ch.puzzle.okr.Constants.QUARTER;
import static ch.puzzle.okr.test.TestConstants.GJ_FOR_TESTS_QUARTER_ID;
import static ch.puzzle.okr.test.TestConstants.GJ_FOR_TEST_QUARTER_LABEL;
-import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
-import static org.springframework.http.HttpStatus.BAD_REQUEST;
-import static org.springframework.http.HttpStatus.NOT_FOUND;
@SpringIntegrationTest
class QuarterPersistenceServiceIT {
@@ -39,40 +35,6 @@ void tearDown() {
TenantContext.setCurrentTenant(null);
}
- @Test
- void shouldReturnSingleQuarterWhenFindingByValidId() {
- Quarter returnedQuarter = quarterPersistenceService.findById(GJ_FOR_TESTS_QUARTER_ID);
-
- assertEquals(GJ_FOR_TESTS_QUARTER_ID, returnedQuarter.getId());
- assertEquals(GJ_FOR_TEST_QUARTER_LABEL, returnedQuarter.getLabel());
- assertEquals(LocalDate.of(2000, 7, 1), returnedQuarter.getStartDate());
- assertEquals(LocalDate.of(2000, 9, 30), returnedQuarter.getEndDate());
- }
-
- @Test
- void shouldThrowExceptionWhenFindingQuarterNotFound() {
- OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class,
- () -> quarterPersistenceService.findById(321L));
-
- List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Quarter", "321")));
-
- assertEquals(NOT_FOUND, exception.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(exception.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason()));
- }
-
- @Test
- void shouldThrowExceptionWhenFindingQuarterWithIdNull() {
- OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class,
- () -> quarterPersistenceService.findById(null));
-
- List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Quarter")));
-
- assertEquals(BAD_REQUEST, exception.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(exception.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason()));
- }
-
@DisplayName("getMostCurrentQuarters() should return current quarter and future quarter and GJForTests quarter")
@Test
void getMostCurrentQuartersShouldReturnCurrentQuarterAndFutureQuarterAndGJForTestsQuarter() {
@@ -141,4 +103,10 @@ void findByLabelShouldReturnNullWhenLabelIsNull() {
// assert
assertNull(returnedQuarter);
}
+
+ @DisplayName("getModelName() should return Quarter")
+ @Test
+ void getModelNameShouldReturnQuarter() {
+ assertEquals(QUARTER, quarterPersistenceService.getModelName());
+ }
}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java
index f71c9daa79..f37812cb21 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java
@@ -1,29 +1,22 @@
package ch.puzzle.okr.service.persistence;
-import ch.puzzle.okr.test.TestHelper;
-import ch.puzzle.okr.dto.ErrorDto;
-import ch.puzzle.okr.exception.OkrResponseStatusException;
import ch.puzzle.okr.models.Team;
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.test.SpringIntegrationTest;
-import org.junit.jupiter.api.AfterEach;
+import ch.puzzle.okr.test.TestHelper;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.server.ResponseStatusException;
import java.util.List;
-import static ch.puzzle.okr.test.TestConstants.TEAM_PUZZLE;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.springframework.http.HttpStatus.*;
+import static ch.puzzle.okr.Constants.TEAM;
+import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringIntegrationTest
class TeamPersistenceServiceIT {
- private static final String NEW_TEAM = "New Team";
- private Team createdTeam;
@Autowired
private TeamPersistenceService teamPersistenceService;
@@ -32,112 +25,20 @@ void setUp() {
TenantContext.setCurrentTenant(TestHelper.SCHEMA_PITC);
}
- @AfterEach
- void tearDown() {
- try {
- if (createdTeam != null) {
- teamPersistenceService.findById(createdTeam.getId());
- teamPersistenceService.deleteById(createdTeam.getId());
- }
- } catch (ResponseStatusException ex) {
- // created team already deleted
- } finally {
- createdTeam = null;
- }
- TenantContext.setCurrentTenant(null);
- }
-
- @Test
- void getTeamByIdShouldReturnTeam() throws ResponseStatusException {
- Team team = teamPersistenceService.findById(5L);
-
- assertEquals(5L, team.getId());
- assertEquals(TEAM_PUZZLE, team.getName());
- }
-
- @Test
- void getTeamByIdShouldThrowExceptionWhenTeamNotFound() {
- OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class,
- () -> teamPersistenceService.findById(321L));
-
- List expectedErrors = List.of(new ErrorDto("MODEL_WITH_ID_NOT_FOUND", List.of("Team", "321")));
-
- assertEquals(NOT_FOUND, exception.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(exception.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason()));
- }
-
- @Test
- void getTeamByIdShouldThrowExceptionWhenTeamIdIsNull() {
- OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class,
- () -> teamPersistenceService.findById(null));
-
- List expectedErrors = List.of(new ErrorDto("ATTRIBUTE_NULL", List.of("ID", "Team")));
-
- assertEquals(BAD_REQUEST, exception.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(exception.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason()));
- }
-
- @Test
- void shouldSaveANewTeam() {
- Team team = Team.Builder.builder().withName("TestTeam").build();
-
- createdTeam = teamPersistenceService.save(team);
- assertNotNull(createdTeam.getId());
- assertEquals("TestTeam", createdTeam.getName());
- }
-
+ // uses data from V100_0_0__TestData.sql
+ @DisplayName("findTeamsByName() should return teams with matching name")
@Test
- void shouldUpdateTeamProperly() {
- Team team = Team.Builder.builder().withName(NEW_TEAM).build();
- createdTeam = teamPersistenceService.save(team);
- createdTeam.setName("Updated Team");
-
- Team returnedTeam = teamPersistenceService.save(createdTeam);
-
- assertEquals(createdTeam.getId(), returnedTeam.getId());
- assertEquals("Updated Team", returnedTeam.getName());
- }
-
- @Test
- void updateTeamShouldThrowExceptionWhenAlreadyUpdated() {
- Team team = Team.Builder.builder().withVersion(1).withName(NEW_TEAM).build();
- createdTeam = teamPersistenceService.save(team);
- Team changedTeam = Team.Builder.builder().withId(createdTeam.getId()).withVersion(0).withName("Changed Team")
- .build();
-
- OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class,
- () -> teamPersistenceService.save(changedTeam));
- List expectedErrors = List.of(new ErrorDto("DATA_HAS_BEEN_UPDATED", List.of("Team")));
-
- assertEquals(UNPROCESSABLE_ENTITY, exception.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(exception.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason()));
- }
-
- @Test
- void shouldDeleteTeam() {
- Team team = Team.Builder.builder().withName(NEW_TEAM).build();
- createdTeam = teamPersistenceService.save(team);
- teamPersistenceService.deleteById(createdTeam.getId());
-
- OkrResponseStatusException exception = assertThrows(OkrResponseStatusException.class,
- () -> teamPersistenceService.findById(createdTeam.getId()));
-
- List expectedErrors = List
- .of(ErrorDto.of("MODEL_WITH_ID_NOT_FOUND", List.of("Team", createdTeam.getId())));
+ void findTeamsByNameShouldReturnTeamsWithMatchingName() {
+ List teams = teamPersistenceService.findTeamsByName("LoremIpsum");
- assertEquals(NOT_FOUND, exception.getStatusCode());
- assertThat(expectedErrors).hasSameElementsAs(exception.getErrors());
- assertTrue(TestHelper.getAllErrorKeys(expectedErrors).contains(exception.getReason()));
+ assertEquals(1, teams.size());
+ assertEquals(6, teams.get(0).getId());
+ assertEquals("LoremIpsum", teams.get(0).getName());
}
+ @DisplayName("getModelName() should return Team")
@Test
- void shouldFindTeamsByName() {
- Team team = Team.Builder.builder().withName(NEW_TEAM).build();
- createdTeam = teamPersistenceService.save(team);
- List teams = teamPersistenceService.findTeamsByName(NEW_TEAM);
- assertThat(teams).contains(createdTeam);
+ void getModelNameShouldReturnTeam() {
+ assertEquals(TEAM, teamPersistenceService.getModelName());
}
}
\ No newline at end of file
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/UserPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/UserPersistenceServiceIT.java
index ca1dc10624..0104060b48 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/persistence/UserPersistenceServiceIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/UserPersistenceServiceIT.java
@@ -3,24 +3,23 @@
import ch.puzzle.okr.models.User;
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.test.SpringIntegrationTest;
-import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.server.ResponseStatusException;
import java.util.List;
+import java.util.Optional;
+import static ch.puzzle.okr.Constants.USER;
+import static ch.puzzle.okr.util.CollectionUtils.iterableToList;
import static org.junit.jupiter.api.Assertions.*;
@SpringIntegrationTest
class UserPersistenceServiceIT {
- private static final String EMAIL_ALICE = "wunderland@puzzle.ch";
-
- User createdUser;
+ private User createdUser;
@Autowired
private UserPersistenceService userPersistenceService;
@@ -39,63 +38,144 @@ void tearDown() {
TenantContext.setCurrentTenant(null);
}
+ @DisplayName("save() should save user with empty user team list")
@Test
- void shouldReturnAllUsersCorrect() throws ResponseStatusException {
- List userList = userPersistenceService.findAll();
+ void saveShouldSaveUserWithEmptyUserTeamList() {
+ // arrange
+ var newUser = User.Builder.builder() //
+ .withFirstname("Hans") //
+ .withLastname("Muster") //
+ .withEmail("muster@puzzle.ch") //
+ .withUserTeamList(List.of()).build();
+
+ // act
+ createdUser = userPersistenceService.save(newUser);
+
+ // assert
+ assertNotNull(createdUser.getId());
+ assertUser("Hans", "Muster", "muster@puzzle.ch", createdUser);
+ }
- Assertions.assertThat(userList.size()).isGreaterThanOrEqualTo(7);
+ @DisplayName("save() should save user with null value for user team list")
+ @Test
+ void saveShouldSaveUserWithNullUserTeamList() {
+ // arrange
+ var newUser = User.Builder.builder() //
+ .withFirstname("Hans") //
+ .withLastname("Muster") //
+ .withEmail("muster@puzzle.ch") //
+ .withUserTeamList(null).build();
+
+ // act
+ createdUser = userPersistenceService.save(newUser);
+
+ // assert
+ assertNotNull(createdUser.getId());
+ assertUser("Hans", "Muster", "muster@puzzle.ch", createdUser);
}
+ @DisplayName("saveAll() should save all users in the input list")
@Test
- void shouldReturnSingleUserWhenFindingOwnerByValidId() {
- User returnedUser = userPersistenceService.findById(1L);
+ void saveAllShouldSaveAllUsersInTheInputList() {
+ // arrange
+ var newUser = User.Builder.builder() //
+ .withFirstname("Hans") //
+ .withLastname("Muster") //
+ .withEmail("muster@puzzle.ch") //
+ .build();
+
+ // act
+ var createdUsers = iterableToList(userPersistenceService.saveAll(List.of(newUser)));
+
+ // assert
+ assertEquals(1, createdUsers.size());
+ createdUser = createdUsers.get(0);
- assertEquals(1L, returnedUser.getId());
- assertEquals("Paco", returnedUser.getFirstname());
- assertEquals("Eggimann", returnedUser.getLastname());
- assertEquals("peggimann@puzzle.ch", returnedUser.getEmail());
+ assertNotNull(createdUser.getId());
+ assertUser("Hans", "Muster", "muster@puzzle.ch", createdUser);
}
+ @DisplayName("getOrCreateUser() should return single user when user found")
@Test
- void shouldThrowExceptionWhenFindingOwnerNotFound() {
- ResponseStatusException exception = assertThrows(ResponseStatusException.class,
- () -> userPersistenceService.findById(321L));
+ void getOrCreateUserShouldReturnSingleUserWhenUserFound() {
+ // arrange
+ var existingUser = User.Builder.builder().withEmail("wunderland@puzzle.ch").build();
- assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode());
- assertEquals("MODEL_WITH_ID_NOT_FOUND", exception.getReason());
+ // act
+ var returnedUser = userPersistenceService.getOrCreateUser(existingUser);
+
+ // assert
+ assertUser(11L, "Alice", "Wunderland", "wunderland@puzzle.ch", returnedUser);
}
+ @DisplayName("getOrCreateUser() should return saved user when user not found")
@Test
- void shouldThrowExceptionWhenFindingOwnerWithNullId() {
- ResponseStatusException exception = assertThrows(ResponseStatusException.class,
- () -> userPersistenceService.findById(null));
+ void getOrCreateUserShouldReturnSavedUserWhenUserNotFound() {
+ // arrange
+ var newUser = User.Builder.builder() //
+ .withId(null) //
+ .withFirstname("firstname") //
+ .withLastname("lastname") //
+ .withEmail("lastname@puzzle.ch") //
+ .build();
+
+ // act
+ createdUser = userPersistenceService.getOrCreateUser(newUser);
- assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
- assertEquals("ATTRIBUTE_NULL", exception.getReason());
+ // assert
+ assertNotNull(createdUser.getId());
+ assertUser("firstname", "lastname", "lastname@puzzle.ch", createdUser);
}
+ // uses data from V100_0_0__TestData.sql
+ @DisplayName("findByEmail() should return user if email is found")
@Test
- void getOrCreateUserShouldReturnSingleUserWhenUserFound() {
- User existingUser = User.Builder.builder().withEmail(EMAIL_ALICE).build();
+ void findByEmailShouldReturnUserIfEmailIsFound() {
+ Optional user = userPersistenceService.findByEmail("gl@gl.com");
- User returnedUser = userPersistenceService.getOrCreateUser(existingUser);
+ assertTrue(user.isPresent());
+ assertEquals("Jaya", user.get().getFirstname());
+ assertEquals("Norris", user.get().getLastname());
+ }
- assertEquals(11L, returnedUser.getId());
- assertEquals("Alice", returnedUser.getFirstname());
- assertEquals("Wunderland", returnedUser.getLastname());
- assertEquals("wunderland@puzzle.ch", returnedUser.getEmail());
+ @DisplayName("findByEmail() should return empty optional if email is not found")
+ @Test
+ void findByEmailShouldReturnEmptyOptionalIfEmailIsNotFound() {
+ assertTrue(userPersistenceService.findByEmail("not_valid@gl.com").isEmpty());
}
+ @DisplayName("findByEmail() should return empty optional if email is null")
@Test
- void getOrCreateUserShouldReturnSavedUserWhenUserNotFound() {
- User newUser = User.Builder.builder().withId(null).withFirstname("firstname").withLastname("lastname")
- .withEmail("lastname@puzzle.ch").build();
+ void findByEmailShouldReturnEmptyOptionalIfEmailIsNull() {
+ assertTrue(userPersistenceService.findByEmail(null).isEmpty());
+ }
- createdUser = userPersistenceService.getOrCreateUser(newUser);
+ // uses data from V100_0_0__TestData.sql
+ @DisplayName("findAllOkrChampions() should return all okr champions")
+ @Test
+ void findAllOkrChampionsShouldReturnAllOkrChampions() {
+ // act
+ var allOkrChampions = userPersistenceService.findAllOkrChampions();
- assertNotNull(createdUser.getId());
- assertEquals("firstname", createdUser.getFirstname());
- assertEquals("lastname", createdUser.getLastname());
- assertEquals("lastname@puzzle.ch", createdUser.getEmail());
+ // assert
+ assertEquals(1, allOkrChampions.size());
+ assertUser(61L, "Jaya", "Norris", "gl@gl.com", allOkrChampions.get(0));
+ }
+
+ @DisplayName("getModelName() should return user")
+ @Test
+ void getModelNameShouldReturnUser() {
+ assertEquals(USER, userPersistenceService.getModelName());
+ }
+
+ private void assertUser(Long id, String firstName, String lastName, String email, User currentUser) {
+ assertEquals(id, currentUser.getId());
+ assertUser(firstName, lastName, email, currentUser);
+ }
+
+ private void assertUser(String firstName, String lastName, String email, User currentUser) {
+ assertEquals(firstName, currentUser.getFirstname());
+ assertEquals(lastName, currentUser.getLastname());
+ assertEquals(email, currentUser.getEmail());
}
}
\ No newline at end of file
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/UserTeamPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/UserTeamPersistenceServiceIT.java
new file mode 100644
index 0000000000..cc2dfe71b8
--- /dev/null
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/UserTeamPersistenceServiceIT.java
@@ -0,0 +1,98 @@
+package ch.puzzle.okr.service.persistence;
+
+import ch.puzzle.okr.multitenancy.TenantContext;
+import ch.puzzle.okr.test.SpringIntegrationTest;
+import ch.puzzle.okr.test.TestHelper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+// uses test date from V100_0_0__TestData.sql
+@SpringIntegrationTest
+public class UserTeamPersistenceServiceIT {
+ private static final Long ID_OF_USER_ALICE = 11L; // user Alice is only in team Lorem
+ private static final Long ID_OF_TEAM_LOREM = 6L; // team Lorem has 3 users
+
+ private static final Long ID_OF_USER_BOB = 21L; // user Bob is only in team Cube
+ private static final Long ID_OF_TEAM_CUBE = 8L; // team Cube has 2 users
+
+ @Autowired
+ private UserTeamPersistenceService userTeamPersistenceService;
+
+ @Autowired
+ private UserPersistenceService userPersistenceService;
+
+ @Autowired
+ private TeamPersistenceService teamPersistenceService;
+
+ static {
+ TenantContext.setCurrentTenant(TestHelper.SCHEMA_PITC);
+ }
+
+ @DisplayName("delete() should remove single user from team")
+ @Test
+ @Transactional
+ void deleteShouldRemoveSingleUserFromTeam() {
+ // arrange
+ var user = userPersistenceService.findById(ID_OF_USER_ALICE);
+ var team = teamPersistenceService.findById(ID_OF_TEAM_LOREM);
+
+ // preconditions: user Alice is in team Lorem and team Lorem has 3 users
+ assertUserIsInTeam(ID_OF_USER_ALICE, ID_OF_TEAM_LOREM, 3);
+
+ // arrange
+ var userTeamToRemove = user.getUserTeamList().get(0); // Alice is only in Team Lorem
+
+ // act
+ user.getUserTeamList().remove(userTeamToRemove);
+ team.getUserTeamList().remove(userTeamToRemove);
+ userTeamPersistenceService.delete(userTeamToRemove);
+
+ // assert: user Alice is no longer in team Lorem and team Lorem has 2 users
+ assertUserIsRemovedFromTeam(ID_OF_USER_ALICE, ID_OF_TEAM_LOREM, 2);
+ }
+
+ @DisplayName("deleteAll() should remove list of users from team")
+ @Test
+ @Transactional
+ void deleteAllShouldRemoveListOfUsersFromTeam() {
+ // arrange
+ var user = userPersistenceService.findById(ID_OF_USER_BOB);
+ var team = teamPersistenceService.findById(ID_OF_TEAM_CUBE);
+
+ // preconditions: user Bob is in a team Cube and team Cube has 2 users
+ assertUserIsInTeam(ID_OF_USER_BOB, ID_OF_TEAM_CUBE, 2);
+
+ // arrange
+ var userTeamToRemove = user.getUserTeamList().get(0); // Bos is only in Team Cube
+
+ // act
+ user.getUserTeamList().remove(userTeamToRemove);
+ team.getUserTeamList().remove(userTeamToRemove);
+ userTeamPersistenceService.deleteAll(List.of(userTeamToRemove));
+
+ // assert: user Bob is no longer in team Cube and team Cube has 1 user
+ assertUserIsRemovedFromTeam(ID_OF_USER_BOB, ID_OF_TEAM_CUBE, 1);
+ }
+
+ private void assertUserIsInTeam(Long userId, Long teamId, int expectedUsersInTeam) {
+ var user = userPersistenceService.findById(userId);
+ Assertions.assertEquals(1, user.getUserTeamList().size());
+
+ var team = this.teamPersistenceService.findById(teamId);
+ Assertions.assertEquals(expectedUsersInTeam, team.getUserTeamList().size());
+ }
+
+ private void assertUserIsRemovedFromTeam(Long userId, Long teamId, int expectedUsersInTeam) {
+ var reloadedUser = userPersistenceService.findById(userId);
+ Assertions.assertEquals(0, reloadedUser.getUserTeamList().size());
+
+ var reloadedTeam = this.teamPersistenceService.findById(teamId);
+ Assertions.assertEquals(expectedUsersInTeam, reloadedTeam.getUserTeamList().size());
+ }
+
+}
diff --git a/backend/src/test/java/ch/puzzle/okr/test/TestHelper.java b/backend/src/test/java/ch/puzzle/okr/test/TestHelper.java
index 75d53c1db4..86bb30b3f7 100644
--- a/backend/src/test/java/ch/puzzle/okr/test/TestHelper.java
+++ b/backend/src/test/java/ch/puzzle/okr/test/TestHelper.java
@@ -56,19 +56,22 @@ public static UserTeam defaultUserTeam(Long id, User user) {
}
public static AuthorizationUser defaultAuthorizationUser() {
- return mockAuthorizationUser(1L, FIRSTNAME, LASTNAME, EMAIL);
- }
-
- public static AuthorizationUser userWithoutWriteAllRole() {
- return mockAuthorizationUser(1L, FIRSTNAME, LASTNAME, EMAIL);
+ return mockAuthorizationUser(1L, FIRSTNAME, LASTNAME, EMAIL, false);
}
public static AuthorizationUser mockAuthorizationUser(User user) {
- return mockAuthorizationUser(user.getId(), user.getFirstname(), user.getLastname(), user.getEmail());
+ return mockAuthorizationUser(user.getId(), user.getFirstname(), user.getLastname(), user.getEmail(),
+ user.isOkrChampion());
}
- public static AuthorizationUser mockAuthorizationUser(Long id, String firstname, String lastname, String email) {
- User user = User.Builder.builder().withId(id).withFirstname(firstname).withLastname(lastname).withEmail(email)
+ public static AuthorizationUser mockAuthorizationUser(Long id, String firstname, String lastname, String email,
+ boolean isOkrChampion) {
+ User user = User.Builder.builder() //
+ .withId(id) //
+ .withFirstname(firstname) //
+ .withLastname(lastname) //
+ .withEmail(email) //
+ .withOkrChampion(isOkrChampion) //
.build();
user.setUserTeamList(List.of(defaultUserTeam(1L, user)));
return new AuthorizationUser(user);
diff --git a/docker/dev-with-prod/docker-compose.yml b/docker/dev-with-prod/docker-compose.yml
new file mode 100644
index 0000000000..0fbb4064c0
--- /dev/null
+++ b/docker/dev-with-prod/docker-compose.yml
@@ -0,0 +1,61 @@
+include:
+ - ../docker-compose.yml
+services:
+ spring:
+ tty: true
+ container_name: spring
+ build:
+ context: .
+ dockerfile: local-prod.Dockerfile
+ restart: always
+ environment:
+ SPRING_PROFILES_ACTIVE: dev
+ volumes:
+ - ../../../okr/backend/target:/app-root/backend
+ network_mode: "host"
+ depends_on:
+ maven-init:
+ condition: service_completed_successfully
+
+ maven:
+ tty: true
+ container_name: maven
+ restart: on-failure
+ image: maven:3.9.9-amazoncorretto-21
+ command: mvn fizzed-watcher:run
+ working_dir: /app-root/
+ volumes:
+ - ../../../okr:/app-root
+ - ~/.m2/repository:/root/.m2/repository
+ depends_on:
+ maven-init:
+ condition: service_completed_successfully
+
+ maven-init:
+ tty: true
+ container_name: maven-init
+ image: maven:3.9.9-amazoncorretto-21
+ command: mvn -B clean package -P build-for-docker,debug,no-formatter
+ working_dir: /app-root/
+ volumes:
+ - ../../../okr:/app-root
+ - ~/.m2/repository:/root/.m2/repository
+ depends_on:
+ angular:
+ condition: service_healthy
+
+ angular:
+ container_name: angular
+ image: node:22
+ tty: true
+ restart: on-failure
+ volumes:
+ - ../../../okr:/opt
+ command: [ "/bin/bash", "-c", "cd /opt/frontend && rm -rf dist && npm ci && npm run watch:prod" ]
+ healthcheck:
+ test: bash -c "[ -f /opt/frontend/dist/frontend/index.html ]"
+ interval: 10s
+ retries: 999
+ start_period: 30s
+ timeout: 10s
+
diff --git a/docker/dev-with-prod/local-prod.Dockerfile b/docker/dev-with-prod/local-prod.Dockerfile
new file mode 100644
index 0000000000..a9bfc82e0f
--- /dev/null
+++ b/docker/dev-with-prod/local-prod.Dockerfile
@@ -0,0 +1,12 @@
+FROM alpine:3.20
+
+USER root
+
+RUN apk update && apk add --upgrade curl && apk --no-cache add openjdk21 inotify-tools
+
+RUN adduser --home /app-root --uid 1001 --disabled-password okr
+USER 1001
+
+WORKDIR app-root/backend
+
+ENTRYPOINT ["/bin/sh", "-c", "export BACKEND_VERSION=$(find . -type f -name 'backend-*.jar' -print -quit | sed -n 's/.*backend-\\(.*\\)\\.jar/\\1/p'); if ! unzip -p backend-${BACKEND_VERSION}.jar META-INF/MANIFEST.MF | grep -q 'Main-Class:'; then echo 'Error: no main manifest attribute, exiting.'; exit 1; fi; java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005 -jar backend-${BACKEND_VERSION}.jar & pid=$!; while true; do inotifywait -e modify backend-${BACKEND_VERSION}.jar; exit 1; done"]
\ No newline at end of file
diff --git a/docker/local-prod/docker-compose.yml b/docker/local-prod/docker-compose.yml
deleted file mode 100644
index 0bee2c0f2d..0000000000
--- a/docker/local-prod/docker-compose.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-include:
- - ../docker-compose.yml
-services:
- spring:
- container_name: spring
- build:
- context: .
- dockerfile: local-prod.Dockerfile
- restart: always
- ports:
- - 8080:8080
- environment:
- SPRING_PROFILES_ACTIVE: staging
- LOGGING_LEVEL_ORG_SPRINGFRAMEWORK: debug
- volumes:
- - ../../../okr/backend/target:/app-root/backend
-
- maven:
- container_name: maven
- image: maven:3.9.9-amazoncorretto-21
- command: sh -c "mvn fizzed-watcher:run"
- working_dir: /app-root/
- volumes:
- - ../../../okr:/app-root/
- - ~/.m2/repository:/root/.m2/repository
-
- angular:
- container_name: angular
- image: node:22
- user: "${UID:-1000}:${GID:-1000}"
- volumes:
- - ../../../okr:/opt
- - /etc/passwd:/etc/passwd:ro
- - /etc/group:/etc/group:ro
- command: [ "/bin/bash", "-c", "cd /opt/frontend && npm ci && npm run watch:prod" ]
diff --git a/docker/local-prod/local-prod.Dockerfile b/docker/local-prod/local-prod.Dockerfile
deleted file mode 100644
index 9077d161ac..0000000000
--- a/docker/local-prod/local-prod.Dockerfile
+++ /dev/null
@@ -1,12 +0,0 @@
-FROM alpine:3.20
-
-USER root
-
-RUN apk update && apk add --upgrade curl && apk --no-cache add openjdk17
-
-RUN adduser --home /app-root --uid 1001 --disabled-password okr
-USER 1001
-
-WORKDIR app-root/backend
-
-ENTRYPOINT ["/bin/sh", "-c", "export BACKEND_VERSION=$(find . -type f -name 'backend-*.jar' -print -quit | sed -n 's/.*backend-\\(.*\\)\\.jar/\\1/p'); java -jar backend-${BACKEND_VERSION}.jar"]
\ No newline at end of file
diff --git a/frontend/cypress/e2e/checkIn.cy.ts b/frontend/cypress/e2e/checkIn.cy.ts
index 45728dedc0..6024396ae2 100644
--- a/frontend/cypress/e2e/checkIn.cy.ts
+++ b/frontend/cypress/e2e/checkIn.cy.ts
@@ -131,7 +131,8 @@ describe('OKR Check-in e2e tests', () => {
cy.contains('Letztes Check-in (' + getCurrentDate() + ')');
});
- it('Should generate checkin list', () => {
+ // TODO: Re-enable tests in ticket #1014 https://github.com/puzzle/okr/issues/1014
+ xit('Should generate checkin list', () => {
cy.getByTestId('objective').first().getByTestId('add-keyResult').first().click();
cy.getByTestId('submit').should('be.disabled');
@@ -163,12 +164,12 @@ describe('OKR Check-in e2e tests', () => {
cy.contains(getCurrentDate());
cy.contains('Wert: 30%');
cy.contains('Wert: 50%');
- cy.contains('Confidence: 5 / 10');
- cy.contains('Confidence: 6 / 10');
- cy.contains('Veränderungen: We bought a new house');
- cy.contains('Veränderungen: This was a good idea');
- cy.contains('Massnahmen: We have to buy more PCs');
- cy.contains('Massnahmen: Will be difficult');
+ checkForAttribute('Confidence:', '5 / 10');
+ checkForAttribute('Confidence:', '6 / 10');
+ checkForAttribute('Veränderungen:', 'We bought a new house');
+ checkForAttribute('Veränderungen:', 'This was a good idea');
+ checkForAttribute('Massnahmen:', 'We have to buy more PCs');
+ checkForAttribute('Massnahmen:', 'Will be difficult');
cy.contains('Schliessen');
});
@@ -193,15 +194,16 @@ describe('OKR Check-in e2e tests', () => {
cy.getByTestId('add-check-in').first().click();
cy.fillOutCheckInMetric(30, 5, 'Here we are', 'A cat would be great');
- cy.contains('Aktuell: CHF 30.-');
+ cy.contains('Aktuell: 30 CHF');
cy.getByTestId('show-all-checkins').click();
cy.wait(500);
cy.contains('Check-in History');
+ cy.contains('Wert: 30 CHF');
cy.getByTestId('edit-check-in').first().click();
cy.contains('Here we edit a metric checkin');
- cy.contains('CHF 30.-');
- cy.contains('Confidence um Target Zone (CHF 213.-) zu erreichen');
+ cy.contains('30 CHF');
+ cy.contains('Confidence um Target Zone (213 CHF) zu erreichen');
cy.contains('5/10');
cy.getByTestId('check-in-metric-value').click().clear().type('200');
cy.getByTestId('confidence-slider').realMouseDown();
@@ -211,10 +213,70 @@ describe('OKR Check-in e2e tests', () => {
cy.getByTestId('submit-check-in').click();
cy.wait(200);
- cy.contains('CHF 200.-');
+ cy.contains('200 CHF');
cy.contains('We bought a new sheep');
});
+ it('Should generate right labels in checkin history list', () => {
+ cy.getByTestId('objective').first().getByTestId('add-keyResult').first().click();
+ cy.getByTestId('submit').should('be.disabled');
+ cy.fillOutKeyResult(
+ 'A new KeyResult for checking checkin list',
+ 'EUR',
+ '10',
+ '300',
+ null,
+ null,
+ null,
+ null,
+ 'This is my description',
+ );
+
+ cy.getByTestId('submit').click();
+
+ cy.getByTestId('keyresult').contains('A new KeyResult for checking checkin list').click();
+
+ cy.getByTestId('add-check-in').first().click();
+ cy.fillOutCheckInMetric(30, 5, 'Here we are', 'A cat would be great');
+ cy.contains('Aktuell: 30 EUR');
+ cy.getByTestId('show-all-checkins').click();
+
+ cy.wait(500);
+ cy.contains('Check-in History');
+ cy.contains('Wert: 30 EUR');
+
+ cy.getByTestId('closeButton').click();
+ // Wait this time because of the toaster message
+ cy.wait(2000);
+ cy.getByTestId('close-drawer').click();
+
+ cy.getByTestId('objective').first().getByTestId('add-keyResult').first().click();
+ cy.fillOutKeyResult(
+ 'There is another kr with fte',
+ 'FTE',
+ '10',
+ '300',
+ null,
+ null,
+ null,
+ null,
+ 'This is my description',
+ );
+
+ cy.getByTestId('submit').click();
+
+ cy.getByTestId('keyresult').contains('There is another kr with fte').click();
+
+ cy.getByTestId('add-check-in').first().click();
+ cy.fillOutCheckInMetric(30, 5, 'Here we are', 'A cat would be great');
+ cy.contains('Aktuell: 30 FTE');
+ cy.getByTestId('show-all-checkins').click();
+
+ cy.wait(500);
+ cy.contains('Check-in History');
+ cy.contains('Wert: 30 FTE');
+ });
+
it('Edit ordinal checkin', () => {
cy.getByTestId('objective').first().getByTestId('add-keyResult').first().click();
cy.getByTestId('submit').should('be.disabled');
@@ -307,12 +369,12 @@ describe('OKR Check-in e2e tests', () => {
cy.intercept('**/keyresults/*').as('getKeyResultsAfterSave');
cy.getByTestId('add-check-in').first().click();
- cy.get('#old-value').should('not.exist');
+ cy.getByTestId('old-checkin-value').should('not.exist');
cy.fillOutCheckInMetric(10, 0, 'changeinfo', 'initiatives');
cy.wait('@getKeyResultsAfterSave');
cy.getByTestId('add-check-in').first().click();
- cy.get('#old-value label + div').contains('10 %');
+ cy.contains('Letzter Wert').siblings('div').contains('10%');
});
});
});
@@ -359,3 +421,7 @@ function getCurrentDate() {
return dd_str + '.' + mm_str + '.' + yyyy;
}
+
+function checkForAttribute(title: string, value: string) {
+ cy.get('mat-dialog-container').contains(value).parent().should('contain', title);
+}
diff --git a/frontend/cypress/e2e/duplicated-scoring.cy.ts b/frontend/cypress/e2e/duplicated-scoring.cy.ts
index d1152ced59..6a87dbd243 100644
--- a/frontend/cypress/e2e/duplicated-scoring.cy.ts
+++ b/frontend/cypress/e2e/duplicated-scoring.cy.ts
@@ -9,7 +9,10 @@ describe('e2e test for scoring adjustment on objective duplicate', () => {
});
it('Create ordinal checkin and validate value of scoring component', () => {
+ cy.intercept('POST', '**/keyresults').as('createKeyresult');
cy.createOrdinalKeyresult('stretch keyresult for testing', null);
+ cy.wait('@createKeyresult');
+ cy.contains('stretch keyresult for testing');
cy.getByTestId('keyresult').get(':contains("stretch keyresult for testing")').last().click();
cy.getByTestId('add-check-in').click();
cy.getByTestId(`stretch-radio`).click();
@@ -26,7 +29,7 @@ describe('e2e test for scoring adjustment on objective duplicate', () => {
cy.visit('/?quarter=3');
let scoringBlock1 = cy
- .getByTestId('objective')
+ .get('.objective:contains("A duplicated Objective for this tool")')
.first()
.getByTestId('key-result')
.first()
diff --git a/frontend/cypress/e2e/keyresult.cy.ts b/frontend/cypress/e2e/keyresult.cy.ts
index f7f9ef7b67..aaa5d3b709 100644
--- a/frontend/cypress/e2e/keyresult.cy.ts
+++ b/frontend/cypress/e2e/keyresult.cy.ts
@@ -294,7 +294,7 @@ describe('OKR Overview', () => {
cy.getByTestId('edit-keyResult').click();
cy.getByTestId('delete-keyResult').click();
- cy.getByTestId('confirmYes').click();
+ cy.getByTestId('confirm-yes').click();
cy.contains('Puzzle ITC');
cy.get('A keyresult to delete').should('not.exist');
diff --git a/frontend/cypress/e2e/objective.cy.ts b/frontend/cypress/e2e/objective.cy.ts
index 94c861e59a..afca9c4c76 100644
--- a/frontend/cypress/e2e/objective.cy.ts
+++ b/frontend/cypress/e2e/objective.cy.ts
@@ -28,8 +28,9 @@ describe('OKR Objective e2e tests', () => {
.get('.objective-menu-option')
.contains('Objective veröffentlichen')
.click();
- cy.getByTestId('confirmYes').click();
-
+ cy.contains('Objective veröffentlichen');
+ cy.contains('Soll dieses Objective veröffentlicht werden?');
+ cy.getByTestId('confirm-yes').click();
cy.getByTestId('objective')
.filter(':contains(A objective in state draft)')
.last()
@@ -128,6 +129,10 @@ describe('OKR Objective e2e tests', () => {
.contains('Objective wiedereröffnen')
.click();
+ cy.contains('Objective wiedereröffnen');
+ cy.contains('Soll dieses Objective wiedereröffnet werden?');
+ cy.getByTestId('confirm-yes').click();
+
cy.getByTestId('objective')
.filter(':contains("This objective will be reopened after")')
.last()
@@ -151,7 +156,9 @@ describe('OKR Objective e2e tests', () => {
.click()
.wait(500)
.tabForward();
- cy.focused().click().wait(500);
+ cy.contains('Objective als Draft speichern');
+ cy.contains('Soll dieses Objective als Draft gespeichert werden?');
+ cy.getByTestId('confirm-yes').click();
cy.getByTestId('objective')
.filter(':contains("This objective will be returned to draft state")')
diff --git a/frontend/cypress/e2e/overview.cy.ts b/frontend/cypress/e2e/overview.cy.ts
index 9f472d899b..a8515217f4 100644
--- a/frontend/cypress/e2e/overview.cy.ts
+++ b/frontend/cypress/e2e/overview.cy.ts
@@ -19,6 +19,6 @@ describe('OKR Overview', () => {
it('Check font ', () => {
cy.get('.team-title').first().invoke('css', 'font-family').should('eq', 'Roboto, sans-serif');
- cy.get('.team-title').first().invoke('css', 'font-weight').should('eq', '700');
+ cy.get('.team-title').first().invoke('css', 'font-weight').should('eq', '600');
});
});
diff --git a/frontend/cypress/e2e/tab.cy.ts b/frontend/cypress/e2e/tab.cy.ts
index 4d1cdd714b..343f1b979d 100644
--- a/frontend/cypress/e2e/tab.cy.ts
+++ b/frontend/cypress/e2e/tab.cy.ts
@@ -72,7 +72,7 @@ describe('Tab workflow tests', () => {
}
function openKeyresultDetail() {
- cy.get('.objective').first().focus();
+ cy.get("[src='assets/icons/ongoing-icon.svg']").parentsUntil('#objective-column').last().focus();
cy.tabForwardUntil('[data-testId="key-result"]');
cy.focused().contains('Fail');
cy.focused().contains('Commit');
@@ -124,6 +124,7 @@ describe('Tab workflow tests', () => {
cy.loginAsUser(users.gl);
onlyOn('chrome');
cy.tabForward();
+ cy.tabForward();
});
// Header from here
@@ -211,13 +212,17 @@ describe('Tab workflow tests', () => {
editInputFields('Edited by Cypress too');
cy.tabForward();
cy.tabForward();
+ cy.focused().contains('Speichern');
cy.realPress('Enter');
- cy.contains('Edited by Cypress');
+ cy.focused().invoke('attr', 'data-testid').should('contain', 'three-dot-menu');
+ cy.focused().parentsUntil('#objective-column').last().contains('Edited by Cypress');
});
it('Duplicate objective with tab', () => {
openThreeDotMenu();
cy.realPress('ArrowDown');
+ cy.realPress('ArrowDown');
+ cy.realPress('ArrowDown');
cy.focused().contains('Objective duplizieren');
cy.realPress('Enter');
cy.wait(500);
@@ -241,7 +246,6 @@ describe('Tab workflow tests', () => {
it('Complete objective dialog with tab', () => {
openThreeDotMenu();
cy.realPress('ArrowDown');
- cy.realPress('ArrowDown');
cy.focused().contains('Objective abschliessen');
cy.realPress('Enter');
cy.wait(500);
@@ -416,7 +420,7 @@ describe('Tab workflow tests', () => {
cy.tabForwardUntil('[data-testId="add-action-plan-line"]');
cy.tabBackward();
cy.realPress('Enter');
- cy.tabForwardUntil('[data-testId="confirmYes"]');
+ cy.tabForwardUntil('[data-testId="confirm-yes"]');
cy.realPress('Enter');
cy.tabForward();
cy.tabForwardUntil('[data-testId="submit"]');
diff --git a/frontend/cypress/e2e/teammanagement.cy.ts b/frontend/cypress/e2e/teammanagement.cy.ts
index d89d8b06e1..6f595384cd 100644
--- a/frontend/cypress/e2e/teammanagement.cy.ts
+++ b/frontend/cypress/e2e/teammanagement.cy.ts
@@ -5,6 +5,40 @@ describe('Team management tests', () => {
const teamName = uniqueSuffix('New Team');
const nameEsha = users.bl.name;
+ describe('Routing to overview', () => {
+ beforeEach(() => {
+ cy.loginAsUser(users.gl);
+ });
+ it('should navigate to overview when clicking logo', () => {
+ cy.getByTestId('team-management').click();
+ cy.getByTestId('logo').click();
+ cy.url().should('not.include', 'team-management');
+ });
+ it('should navigate to overview when pressing back to overview', () => {
+ cy.getByTestId('team-management').click();
+ cy.getByTestId('routerLink-to-overview').click();
+ cy.url().should('not.include', 'team-management');
+ });
+ it('should preserve team filter', () => {
+ cy.get('mat-chip:visible:contains("/BBT")').click();
+ cy.get('mat-chip:visible:contains("Puzzle ITC")').click();
+ checkTeamsSelected();
+ cy.getByTestId('team-management').click();
+ checkTeamsSelected();
+ cy.getByTestId('routerLink-to-overview').click();
+ checkTeamsSelected();
+ cy.getByTestId('team-management').click();
+ cy.getByTestId('logo').click();
+ checkTeamsSelected();
+ });
+
+ function checkTeamsSelected() {
+ cy.url().should('include', 'teams=');
+ cy.url().should('include', '6');
+ cy.url().should('include', '4');
+ }
+ });
+
describe('As GL', () => {
before(() => {
// login as bl to ensure this user exists in database
@@ -44,8 +78,8 @@ describe('Team management tests', () => {
cy.getByTestId('remove-from-member-list').click();
// dialog
- cy.contains(`Jaya Norris wirklich aus Team ${teamName} entfernen?`);
- cy.getByTestId('cancelDialog-confirm').click();
+ cy.contains(`Möchtest du Jaya Norris wirklich aus dem Team '${teamName}' entfernen?`);
+ cy.getByTestId('confirm-yes').click();
cy.wait('@removeUser');
@@ -60,8 +94,8 @@ describe('Team management tests', () => {
cy.getByTestId('remove-from-member-list').click();
// cancel dialog
- cy.contains(`Jaya Norris wirklich aus Team ${teamName} entfernen?`);
- cy.getByTestId('cancelDialog-cancel').click();
+ cy.contains(`Möchtest du Jaya Norris wirklich aus dem Team '${teamName}' entfernen?`);
+ cy.getByTestId('confirm-no').click();
cy.get('@removeUser.all').then((interceptions) => {
expect(interceptions).to.have.length(0);
@@ -96,14 +130,18 @@ describe('Team management tests', () => {
cy.getByTestId('teamDeleteButton').click();
// cancel dialog => cancel
- cy.contains(`${teamName} wirklich löschen?`);
- cy.getByTestId('cancelDialog-cancel').click();
+ cy.contains(
+ `Möchtest du das Team '${teamName}' wirklich löschen? Zugehörige Objectives werden dadurch in allen Quartalen ebenfalls gelöscht!`,
+ );
+ cy.getByTestId('confirm-no').click();
// try again and confirm dialog
cy.getByTestId('teamMoreButton').click();
cy.getByTestId('teamDeleteButton').click();
- cy.contains(`${teamName} wirklich löschen?`);
- cy.getByTestId('cancelDialog-confirm').click();
+ cy.contains(
+ `Möchtest du das Team '${teamName}' wirklich löschen? Zugehörige Objectives werden dadurch in allen Quartalen ebenfalls gelöscht!`,
+ );
+ cy.getByTestId('confirm-yes').click();
cy.wait(['@saveTeam', '@getUsers']);
@@ -149,6 +187,10 @@ describe('Team management tests', () => {
const firstNameStefan = uniqueSuffix('Stefan');
cy.getByTestId('invite-member').click();
+ cy.wait(1000); // wait for dialog to open
+ cy.tabForward();
+ cy.contains('Members registrieren');
+
fillOutNewUser(firstNameClaudia, 'Meier', mailUserClaudia);
cy.tabForward();
cy.tabForward();
@@ -305,7 +347,7 @@ describe('Team management tests', () => {
// add findus peterson
cy.getByTestId('search-member-to-add').click().type('Find', { delay: 1 });
- cy.get(matOption).contains('Findus Peterson').click();
+ cy.contains(matOption, 'Findus Peterson').click();
// add robin papierer
cy.getByTestId('search-member-to-add').click();
@@ -336,6 +378,7 @@ describe('Team management tests', () => {
return;
}
$row.find(`[data-testId='edit-role']`).click();
+ cy.wait(500); // wait for dialog to open
})
.then(() => {
cy.getByTestId('select-team-role').click();
@@ -356,7 +399,7 @@ describe('Team management tests', () => {
it('should remove BBT membership of findus', () => {
navigateToUser('Findus Peterson');
cy.getByTestId('delete-team-member').click();
- cy.getByTestId('cancelDialog-confirm').click();
+ cy.getByTestId('confirm-yes').click();
cy.get('app-member-detail').contains('/BBT').should('not.exist');
});
@@ -365,12 +408,12 @@ describe('Team management tests', () => {
navigateToUser(nameEsha);
cy.getByTestId('delete-team-member').eq(0).click();
- cy.getByTestId('cancelDialog-confirm').click();
+ cy.getByTestId('confirm-yes').click();
cy.wait('@removeUser');
cy.getByTestId('delete-team-member').eq(0).click();
- cy.getByTestId('cancelDialog-confirm').click();
+ cy.getByTestId('confirm-yes').click();
cy.get('app-member-detail').should('not.contain', '/BBT').and('not.contain', 'LoremIpsum');
});
diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts
index 1fa1df2314..30e6b962c6 100644
--- a/frontend/cypress/support/commands.ts
+++ b/frontend/cypress/support/commands.ts
@@ -1,6 +1,7 @@
import { validateScoring } from './scoringSupport';
Cypress.Commands.add('loginAsUser', (user: any) => {
+ cy.viewport(1920, 1080);
loginWithCredentials(user.username, user.password);
overviewIsLoaded();
});
diff --git a/frontend/package.json b/frontend/package.json
index 4c12464841..08e2415442 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -3,7 +3,7 @@
"version": "2.0.0",
"scripts": {
"ng": "ng",
- "start": "ng serve ",
+ "start": "ng serve",
"build": "ng build",
"build:staging": "ng build --configuration staging",
"watch": "ng build --watch --configuration development",
diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html
index 8b3716657a..a6df9fb465 100644
--- a/frontend/src/app/app.component.html
+++ b/frontend/src/app/app.component.html
@@ -1,2 +1,6 @@
-
-
+
+
+
+
+
+
diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss
index 97a874648e..8ba091ef8b 100644
--- a/frontend/src/app/app.component.scss
+++ b/frontend/src/app/app.component.scss
@@ -1,12 +1,3 @@
-.okr-label {
- margin-left: 35px;
-}
-
-.overview-item {
- margin-right: 20px;
- margin-left: 50px;
-}
-
app-application-top-bar {
z-index: 1001;
position: relative;
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 58fa564e59..6e3150d3c1 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -166,9 +166,9 @@ export const MY_FORMATS = {
MatChipsModule,
CdkDropList,
CdkDrag,
- SharedModule,
A11yModule,
CdkDragHandle,
+ SharedModule,
],
providers: [
{
diff --git a/frontend/src/app/callback/callback.component.html b/frontend/src/app/callback/callback.component.html
deleted file mode 100644
index 9a5f2cf345..0000000000
--- a/frontend/src/app/callback/callback.component.html
+++ /dev/null
@@ -1 +0,0 @@
-