Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- **Tests**: A JDBC URL `jdbc:h2:mem:testdb;MODE=NOT_A_MODE` is rejected with an SQLException that names the unknown mode. `POST /api/connections/test` returns HTTP 400 for that URL. `GET /api/connections/{id}/test` no longer repeats the same rejection ([#314](https://github.com/sitmun/sitmun-backend-core/issues/314)).
- **Tests**: Database connection, role, territory, territory group type, and user repository tests use MockMvc on the shared context. They no longer boot an embedded server ([#316](https://github.com/sitmun/sitmun-backend-core/issues/316)).
- **Tests**: `UserChecksServiceTest` calls `getWarnings` without a Spring context. `JsonWebTokenServiceTest` loads only `JsonWebTokenService` with a fixed clock. `SpatialReferenceSystemTest`, `UserPositionBusinessLogicTest`, and `UserPositionRepositoryDataRestTest` reuse an existing suite context. Password reset, MIA render security, language change, task literal enrollment, and database connection tests name why their context stays unique ([#315](https://github.com/sitmun/sitmun-backend-core/issues/315)).
- **Tests**: `TerritoryMembershipAssociationWriteTest` locks `PUT /api/territories/{id}/members` and `PUT /api/territories/{id}/memberOf` on `STM_GRP_TER`. `ApplicationRoleAssociationWriteTest` locks `PUT /api/applications/{id}/availableRoles` and `PUT /api/roles/{id}/applications` on `STM_APP_ROL`. Each side stays an owning association. Replacing one side leaves the other side's join row ([#297](https://github.com/sitmun/sitmun-backend-core/issues/297)).

### Security

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package org.sitmun.domain.application;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.sitmun.test.URIConstants.APPLICATION_URI;
import static org.sitmun.test.URIConstants.APPLICATION_URI_AVAILABLE_ROLES;
import static org.sitmun.test.URIConstants.ROLE_APPLICATIONS_URI;
import static org.sitmun.test.URIConstants.ROLE_URI;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.sitmun.domain.role.Role;
import org.sitmun.domain.role.RoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("Application role association writes")
class ApplicationRoleAssociationWriteTest {

@Autowired private MockMvc mvc;
@Autowired private ApplicationRepository applicationRepository;
@Autowired private RoleRepository roleRepository;
@Autowired private JdbcTemplate jdbcTemplate;

private final List<Application> applications = new ArrayList<>();
private final List<Role> roles = new ArrayList<>();

@AfterEach
void cleanup() {
for (Application application : applications) {
jdbcTemplate.update("DELETE FROM STM_APP_ROL WHERE ARO_APPID = ?", application.getId());
}
for (Role role : roles) {
jdbcTemplate.update("DELETE FROM STM_APP_ROL WHERE ARO_ROLEID = ?", role.getId());
}
applications.forEach(application -> applicationRepository.deleteById(application.getId()));
roles.forEach(role -> roleRepository.deleteById(role.getId()));
applications.clear();
roles.clear();
}

@Test
@DisplayName("PUT availableRoles inserts one STM_APP_ROL row visible from both sides")
@WithMockUser(roles = "ADMIN")
void putAvailableRolesInsertsOneJoinRowVisibleFromBothSides() throws Exception {
Application application = application("app");
Role role = role("role");

putAvailableRoles(application, role);

assertThat(joinRows(application.getId(), role.getId())).isEqualTo(1);
assertAvailableRolesContains(application, role);
assertApplicationsContains(role, application);
}

@Test
@DisplayName("PUT applications inserts the same STM_APP_ROL pair")
@WithMockUser(roles = "ADMIN")
void putApplicationsInsertsTheSamePair() throws Exception {
Application application = application("app");
Role role = role("role");

putApplications(role, application);

assertThat(joinRows(application.getId(), role.getId())).isEqualTo(1);
assertAvailableRolesContains(application, role);
assertApplicationsContains(role, application);
}

@Test
@DisplayName("Replacing availableRoles leaves the applications row")
@WithMockUser(roles = "ADMIN")
void replacingAvailableRolesLeavesTheApplicationsRow() throws Exception {
Application center = application("center");
Application other = application("other");
Role childRole = role("child");
Role replacement = role("next");

putApplications(childRole, other);
putAvailableRoles(center, childRole);

putAvailableRoles(center, replacement);

assertThat(joinRows(other.getId(), childRole.getId())).isEqualTo(1);
assertThat(joinRows(center.getId(), replacement.getId())).isEqualTo(1);
assertThat(joinRows(center.getId(), childRole.getId())).isZero();
assertApplicationsContains(childRole, other);
assertAvailableRolesContains(center, replacement);
}

@Test
@DisplayName("Replacing applications leaves the availableRoles row")
@WithMockUser(roles = "ADMIN")
void replacingApplicationsLeavesTheAvailableRolesRow() throws Exception {
Application center = application("center");
Application replacement = application("next");
Role childRole = role("child");
Role parentRole = role("parent");

putAvailableRoles(center, childRole);
putApplications(parentRole, center);

putApplications(parentRole, replacement);

assertThat(joinRows(center.getId(), childRole.getId())).isEqualTo(1);
assertThat(joinRows(replacement.getId(), parentRole.getId())).isEqualTo(1);
assertThat(joinRows(center.getId(), parentRole.getId())).isZero();
assertAvailableRolesContains(center, childRole);
assertApplicationsContains(parentRole, replacement);
}

private Application application(String label) {
String suffix = UUID.randomUUID().toString().substring(0, 8);
Application application =
applicationRepository.save(
Application.builder()
.name(label + "-" + suffix)
.type("I")
.jspTemplate("")
.createdDate(Date.from(Instant.now()))
.build());
applications.add(application);
return application;
}

private Role role(String label) {
String suffix = UUID.randomUUID().toString().substring(0, 8);
Role role = roleRepository.save(Role.builder().name(label + "-" + suffix).build());
roles.add(role);
return role;
}

private void putAvailableRoles(Application application, Role role) throws Exception {
mvc.perform(
put(APPLICATION_URI_AVAILABLE_ROLES, application.getId())
.content(ROLE_URI.replace("{0}", role.getId().toString()))
.contentType("text/uri-list"))
.andExpect(status().isNoContent());
}

private void putApplications(Role role, Application application) throws Exception {
mvc.perform(
put(ROLE_APPLICATIONS_URI, role.getId())
.content(APPLICATION_URI.replace("{0}", application.getId().toString()))
.contentType("text/uri-list"))
.andExpect(status().isNoContent());
}

private void assertAvailableRolesContains(Application application, Role role) throws Exception {
mvc.perform(get(APPLICATION_URI_AVAILABLE_ROLES, application.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.roles[*].name", hasItem(role.getName())));
}

private void assertApplicationsContains(Role role, Application application) throws Exception {
mvc.perform(get(ROLE_APPLICATIONS_URI, role.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.applications[*].name", hasItem(application.getName())));
}

private Integer joinRows(Integer applicationId, Integer roleId) {
return jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM STM_APP_ROL WHERE ARO_APPID = ? AND ARO_ROLEID = ?",
Integer.class,
applicationId,
roleId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package org.sitmun.domain.territory;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.sitmun.test.URIConstants.TERRITORY_URI;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("Territory membership association writes")
class TerritoryMembershipAssociationWriteTest {

private static final String MEMBERS_URI = TERRITORY_URI + "/members";
private static final String MEMBER_OF_URI = TERRITORY_URI + "/memberOf";

@Autowired private MockMvc mvc;
@Autowired private TerritoryRepository territoryRepository;
@Autowired private JdbcTemplate jdbcTemplate;

private final List<Territory> territories = new ArrayList<>();

@AfterEach
void cleanup() {
for (Territory territory : territories) {
jdbcTemplate.update(
"DELETE FROM STM_GRP_TER WHERE GTE_TERID = ? OR GTE_TERMID = ?",
territory.getId(),
territory.getId());
}
territories.forEach(territory -> territoryRepository.deleteById(territory.getId()));
territories.clear();
}

@Test
@DisplayName("PUT members inserts one STM_GRP_TER row visible from both sides")
@WithMockUser(roles = "ADMIN")
void putMembersInsertsOneJoinRowVisibleFromBothSides() throws Exception {
Territory parent = territory("parent");
Territory child = territory("child");

putAssociation(MEMBERS_URI, parent.getId(), child);

assertThat(joinRows(parent.getId(), child.getId())).isEqualTo(1);
assertMembersContains(parent, child);
assertMemberOfContains(child, parent);
}

@Test
@DisplayName("PUT memberOf inserts the same STM_GRP_TER pair")
@WithMockUser(roles = "ADMIN")
void putMemberOfInsertsTheSamePair() throws Exception {
Territory parent = territory("parent");
Territory child = territory("child");

putAssociation(MEMBER_OF_URI, child.getId(), parent);

assertThat(joinRows(parent.getId(), child.getId())).isEqualTo(1);
assertMembersContains(parent, child);
assertMemberOfContains(child, parent);
}

@Test
@DisplayName("Replacing members leaves the member-of row")
@WithMockUser(roles = "ADMIN")
void replacingMembersLeavesTheMemberOfRow() throws Exception {
Territory center = territory("center");
Territory child = territory("child");
Territory parent = territory("parent");
Territory replacement = territory("next");

putAssociation(MEMBERS_URI, center.getId(), child);
putAssociation(MEMBER_OF_URI, center.getId(), parent);

putAssociation(MEMBERS_URI, center.getId(), replacement);

assertThat(joinRows(parent.getId(), center.getId())).isEqualTo(1);
assertThat(joinRows(center.getId(), replacement.getId())).isEqualTo(1);
assertThat(joinRows(center.getId(), child.getId())).isZero();
assertMemberOfContains(center, parent);
assertMembersContains(center, replacement);
}

@Test
@DisplayName("Replacing memberOf leaves the members row")
@WithMockUser(roles = "ADMIN")
void replacingMemberOfLeavesTheMembersRow() throws Exception {
Territory center = territory("center");
Territory child = territory("child");
Territory parent = territory("parent");
Territory replacement = territory("next");

putAssociation(MEMBERS_URI, center.getId(), child);
putAssociation(MEMBER_OF_URI, center.getId(), parent);

putAssociation(MEMBER_OF_URI, center.getId(), replacement);

assertThat(joinRows(center.getId(), child.getId())).isEqualTo(1);
assertThat(joinRows(replacement.getId(), center.getId())).isEqualTo(1);
assertThat(joinRows(parent.getId(), center.getId())).isZero();
assertMembersContains(center, child);
assertMemberOfContains(center, replacement);
}

private Territory territory(String label) {
String suffix = UUID.randomUUID().toString().substring(0, 8);
Territory territory =
territoryRepository.save(
Territory.builder()
.name(label + "-" + suffix)
.code(label + "-" + suffix)
.blocked(false)
.build());
territories.add(territory);
return territory;
}

private void putAssociation(String uri, Integer ownerId, Territory target) throws Exception {
mvc.perform(
put(uri, ownerId)
.content(TERRITORY_URI.replace("{0}", target.getId().toString()))
.contentType("text/uri-list"))
.andExpect(status().isNoContent());
}

private void assertMembersContains(Territory owner, Territory member) throws Exception {
mvc.perform(get(MEMBERS_URI, owner.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.territories[*].name", hasItem(member.getName())));
}

private void assertMemberOfContains(Territory member, Territory parent) throws Exception {
mvc.perform(get(MEMBER_OF_URI, member.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.territories[*].name", hasItem(parent.getName())));
}

private Integer joinRows(Integer territoryId, Integer memberId) {
return jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM STM_GRP_TER WHERE GTE_TERID = ? AND GTE_TERMID = ?",
Integer.class,
territoryId,
memberId);
}
}
Loading