diff --git a/stack/keycloak/migration/add-authorities-basisdaten-wahlvorschlaege.yml b/stack/keycloak/migration/add-authorities-basisdaten-wahlvorschlaege.yml new file mode 100644 index 000000000..ab6a5f4bf --- /dev/null +++ b/stack/keycloak/migration/add-authorities-basisdaten-wahlvorschlaege.yml @@ -0,0 +1,87 @@ +id: add authorities basisdaten wahlvorschlaege +author: MrSebastian +realm: ${SSO_REALM} +changes: + - addRole: + name: Basisdaten_BUSINESSACTION_GetWahlvorschlaege + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_BUSINESSACTION_GetWahlvorschlaege + clientId: ${SSO_CLIENT_ID} + + - addRole: + name: Basisdaten_READ_WLSWahlvorschlaege + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_READ_WLSWahlvorschlaege + clientId: ${SSO_CLIENT_ID} + - addRole: + name: Basisdaten_WRITE_WLSWahlvorschlaege + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_WRITE_WLSWahlvorschlaege + clientId: ${SSO_CLIENT_ID} + - addRole: + name: Basisdaten_DELETE_WLSWahlvorschlaege + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_DELETE_WLSWahlvorschlaege + clientId: ${SSO_CLIENT_ID} + + - addRole: + name: Basisdaten_READ_Wahlvorschlag + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_READ_Wahlvorschlag + clientId: ${SSO_CLIENT_ID} + - addRole: + name: Basisdaten_WRITE_Wahlvorschlag + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_WRITE_Wahlvorschlag + clientId: ${SSO_CLIENT_ID} + - addRole: + name: Basisdaten_DELETE_Wahlvorschlag + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_DELETE_Wahlvorschlag + clientId: ${SSO_CLIENT_ID} + + - addRole: + name: Basisdaten_READ_Kandidat + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_READ_Kandidat + clientId: ${SSO_CLIENT_ID} + - addRole: + name: Basisdaten_WRITE_Kandidat + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_WRITE_Kandidat + clientId: ${SSO_CLIENT_ID} + - addRole: + name: Basisdaten_DELETE_Kandidat + clientRole: true + clientId: ${SSO_CLIENT_ID} + - assignRoleToGroup: + group: allBasisdatenAuthorities + role: Basisdaten_DELETE_Kandidat + clientId: ${SSO_CLIENT_ID} \ No newline at end of file diff --git a/stack/keycloak/migration/create-group-all-basisdaten-authorities.yml b/stack/keycloak/migration/create-group-all-basisdaten-authorities.yml new file mode 100644 index 000000000..fdb657eab --- /dev/null +++ b/stack/keycloak/migration/create-group-all-basisdaten-authorities.yml @@ -0,0 +1,15 @@ +id: create group allBasisdatenAuthorities and link wls_all* +author: MrSebastian +realm: ${SSO_REALM} +changes: + - addGroup: + name: allBasisdatenAuthorities + - assignGroup: + user: wls_all + group: allBasisdatenAuthorities + - assignGroup: + user: wls_all_uwb + group: allBasisdatenAuthorities + - assignGroup: + user: wls_all_bwb + group: allBasisdatenAuthorities \ No newline at end of file diff --git a/stack/keycloak/migration/keycloak-changelog.yml b/stack/keycloak/migration/keycloak-changelog.yml index 493ba1c29..31513aef6 100644 --- a/stack/keycloak/migration/keycloak-changelog.yml +++ b/stack/keycloak/migration/keycloak-changelog.yml @@ -28,4 +28,6 @@ includes: - path: add-authorities-wahlvorbereitung-urnenwahlschliessungsuhrzeit.yml - path: create-group-all-eai-authorities.yml - path: add-authorities-eai-wahlvorstand.yml - - path: add-authorities-wahlvorbereitung-briefwahlvorbereitung.yml \ No newline at end of file + - path: add-authorities-wahlvorbereitung-briefwahlvorbereitung.yml + - path: create-group-all-basisdaten-authorities.yml + - path: add-authorities-basisdaten-wahlvorschlaege.yml \ No newline at end of file diff --git a/wls-basisdaten-service/pom.xml b/wls-basisdaten-service/pom.xml index ce5d4d767..52379108b 100644 --- a/wls-basisdaten-service/pom.xml +++ b/wls-basisdaten-service/pom.xml @@ -36,7 +36,9 @@ 1.18.30 0.2.0 + 1.5.5.Final 2.6.0 + 1.1.0 @@ -178,6 +180,13 @@ 0.2.6 + + + de.muenchen.oss.wahllokalsystem.wls-common + security + ${wls.common.version} + + jakarta.validation @@ -199,6 +208,11 @@ spring-security-test test + + org.springframework.cloud + spring-cloud-starter-contract-stub-runner + test + org.junit.jupiter junit-jupiter-api @@ -209,6 +223,12 @@ junit-jupiter-engine test + + de.muenchen.oss.wahllokalsystem.wls-common + testing + ${wls.common.version} + test + @@ -328,12 +348,25 @@ lombok ${org.projectlombok.lombok.version} + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + org.projectlombok lombok-mapstruct-binding ${org.projectlombok.mapstructbinding.version} + + + -Amapstruct.defaultComponentModel=spring + + + -Amapstruct.unmappedTargetPolicy=ERROR + + diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/MicroServiceApplication.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/MicroServiceApplication.java index f1c57cf6b..43ac60833 100644 --- a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/MicroServiceApplication.java +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/MicroServiceApplication.java @@ -18,7 +18,9 @@ @ComponentScan( basePackages = { "org.springframework.data.jpa.convert.threeten", - "de.muenchen.oss.wahllokalsystem.basisdatenservice" + "de.muenchen.oss.wahllokalsystem.basisdatenservice", + "de.muenchen.oss.wahllokalsystem.wls.common.exception", + "de.muenchen.oss.wahllokalsystem.wls.common.security" } ) @EntityScan( diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/ClientImpl.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/ClientImpl.java new file mode 100644 index 000000000..bdb6fa859 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/ClientImpl.java @@ -0,0 +1,42 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.clients; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.configuration.Profiles; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.client.WahlvorschlagControllerApi; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlaegeDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.exception.ExceptionConstants; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeClient; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeModel; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ExceptionFactory; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Component +@Profile(Profiles.NOT + Profiles.DUMMY_CLIENTS) +@RequiredArgsConstructor +@Slf4j +public class ClientImpl implements WahlvorschlaegeClient { + + private final ExceptionFactory exceptionFactory; + + private final WahlvorschlagControllerApi wahlvorschlagControllerApi; + private final WahlvorschlaegeClientMapper wahlvorschlaegeClientMapper; + + @Override + public WahlvorschlaegeModel getWahlvorschlaege(final BezirkUndWahlID bezirkUndWahlID) { + final WahlvorschlaegeDTO wahlvorschlaege; + try { + wahlvorschlaege = wahlvorschlagControllerApi.loadWahlvorschlaege(bezirkUndWahlID.getWahlID(), bezirkUndWahlID.getWahlbezirkID()); + } catch (final Exception exception) { + log.info("exception on loadwahlvorschlaege from external", exception); + throw exceptionFactory.createTechnischeWlsException(ExceptionConstants.FAILED_COMMUNICATION_WITH_EAI); + } + if (wahlvorschlaege == null) { + throw exceptionFactory.createFachlicheWlsException(ExceptionConstants.NULL_FROM_CLIENT); + } + + return wahlvorschlaegeClientMapper.toModel(wahlvorschlaege); + } +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/DummyClientImpl.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/DummyClientImpl.java new file mode 100644 index 000000000..5b2e10b6e --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/DummyClientImpl.java @@ -0,0 +1,28 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.clients; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.configuration.Profiles; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.KandidatModel; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeClient; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeModel; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlagModel; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import java.util.Set; +import java.util.UUID; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +@Component +@Profile(Profiles.DUMMY_CLIENTS) +public class DummyClientImpl implements WahlvorschlaegeClient { + + @Override + public WahlvorschlaegeModel getWahlvorschlaege(BezirkUndWahlID bezirkUndWahlID) { + return new WahlvorschlaegeModel(bezirkUndWahlID, "stimmzettelgebiedID", + Set.of(new WahlvorschlagModel(UUID.randomUUID().toString(), 1L, "kurzname1", true, + Set.of(new KandidatModel(UUID.randomUUID().toString(), "kandidat11", 1L, true, 1L, true), + new KandidatModel(UUID.randomUUID().toString(), "kandidat21", 2L, false, 1L, false))), + new WahlvorschlagModel(UUID.randomUUID().toString(), 2L, "kurzname2", true, + Set.of(new KandidatModel(UUID.randomUUID().toString(), "kandidat21", 1L, true, 1L, true), + new KandidatModel(UUID.randomUUID().toString(), "kandidat22", 2L, false, 1L, false))))); + } +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/WahlvorschlaegeClientMapper.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/WahlvorschlaegeClientMapper.java new file mode 100644 index 000000000..4a2f17bba --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/WahlvorschlaegeClientMapper.java @@ -0,0 +1,14 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.clients; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlaegeDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeModel; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface WahlvorschlaegeClientMapper { + + @Mapping(target = "bezirkUndWahlID.wahlID", source = "wahlID") + @Mapping(target = "bezirkUndWahlID.wahlbezirkID", source = "wahlbezirkID") + WahlvorschlaegeModel toModel(WahlvorschlaegeDTO wahlvorschlaegeDTO); +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/configuration/Profiles.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/configuration/Profiles.java new file mode 100644 index 000000000..c1fb88f5a --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/configuration/Profiles.java @@ -0,0 +1,12 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.configuration; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Profiles { + + public static final String NOT = "!"; + + public static final String DUMMY_CLIENTS = "dummy.clients"; +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/Kandidat.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/Kandidat.java new file mode 100644 index 000000000..3417c2cbf --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/Kandidat.java @@ -0,0 +1,67 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain; + +import static java.sql.Types.VARCHAR; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotNull; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.UuidGenerator; + +@Entity +@Getter +@Setter +@ToString(onlyExplicitlyIncluded = true) +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +public class Kandidat { + + @Id + @GeneratedValue(generator = "uuid") + @UuidGenerator + @JdbcTypeCode(VARCHAR) + private UUID id; + + @NaturalId + @NotNull + @ToString.Include + private String identifikator; + + @ManyToOne + @NotNull + @JoinColumn(name = "wahlvorschlagID") + @EqualsAndHashCode.Exclude + private Wahlvorschlag wahlvorschlag; + + @NotNull + @ToString.Include + private String name; + + @NotNull + @ToString.Include + private long listenposition; + + @NotNull + @ToString.Include + private boolean direktkandidat; + + @NotNull + @ToString.Include + private long tabellenSpalteInNiederschrift; + + @NotNull + @ToString.Include + private boolean einzelbewerber; +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/KandidatRepository.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/KandidatRepository.java new file mode 100644 index 000000000..3c9a3a217 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/KandidatRepository.java @@ -0,0 +1,37 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain; + +import java.util.UUID; +import org.springframework.data.repository.CrudRepository; +import org.springframework.security.access.prepost.PreAuthorize; + +@PreAuthorize("hasAuthority('Basisdaten_READ_Kandidat')") +public interface KandidatRepository extends CrudRepository { + + @Override + @PreAuthorize("hasAuthority('Basisdaten_WRITE_Kandidat')") + S save(S entity); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_WRITE_Kandidat')") + Iterable saveAll(Iterable entities); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Kandidat')") + void deleteById(UUID id); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Kandidat')") + void delete(Kandidat entity); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Kandidat')") + void deleteAllById(Iterable strings); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Kandidat')") + void deleteAll(Iterable entities); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Kandidat')") + void deleteAll(); +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/Wahlvorschlaege.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/Wahlvorschlaege.java new file mode 100644 index 000000000..8a6ab1f8d --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/Wahlvorschlaege.java @@ -0,0 +1,62 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain; + +import static java.sql.Types.VARCHAR; + +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.UuidGenerator; + +@Entity +@Embeddable +@Getter +@Setter +@ToString(onlyExplicitlyIncluded = true) +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +public class Wahlvorschlaege { + + @Id + @GeneratedValue(generator = "uuid") + @UuidGenerator + @JdbcTypeCode(VARCHAR) + private UUID id; + + @Embedded + @NaturalId + @NotNull + @ToString.Include + private BezirkUndWahlID bezirkUndWahlID; + + @NotNull + @ToString.Include + private String stimmzettelgebietID; + + @OneToMany(mappedBy = "wahlvorschlaeage", orphanRemoval = true) + @NotNull + @Size(min = 1) + private Set wahlvorschlaege = new LinkedHashSet<>(); + + public void addWahlvorschlag(final Wahlvorschlag wahlvorschlag) { + wahlvorschlag.setWahlvorschlaeage(this); + wahlvorschlaege.add(wahlvorschlag); + } +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/WahlvorschlaegeRepository.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/WahlvorschlaegeRepository.java new file mode 100644 index 000000000..05a26a16b --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/WahlvorschlaegeRepository.java @@ -0,0 +1,54 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain; + +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.repository.CrudRepository; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; + +@PreAuthorize("hasAuthority('Basisdaten_READ_WLSWahlvorschlaege')") +@Transactional +public interface WahlvorschlaegeRepository extends CrudRepository { + + String CACHE = "WLSWAHLVORSCHLAEGE_CACHE"; + + @Override + List findAll(); + + @Override + @Cacheable(value = CACHE, key = "#p0") + Optional findById(UUID bezirkUndWahlID); + + Optional findByBezirkUndWahlID(BezirkUndWahlID bezirkUndWahlID); + + @Override + @CachePut(value = CACHE, key = "#p0.bezirkUndWahlID") + @PreAuthorize("hasAuthority('Basisdaten_WRITE_WLSWahlvorschlaege')") + S save(S wahlvorschlaege); + + @Override + @CacheEvict(value = CACHE, key = "#p0") + @PreAuthorize("hasAuthority('Basisdaten_DELETE_WLSWahlvorschlaege')") + void deleteById(UUID bezirkUndWahlID); + + @Override + @CacheEvict(value = CACHE, key = "#p0.bezirkUndWahlID") + @PreAuthorize("hasAuthority('Basisdaten_DELETE_WLSWahlvorschlaege')") + void delete(Wahlvorschlaege entity); + + @Override + @CacheEvict(value = CACHE, allEntries = true) + @PreAuthorize("hasAuthority('Basisdaten_DELETE_WLSWahlvorschlaege')") + void deleteAll(Iterable entities); + + @Override + @CacheEvict(value = CACHE, allEntries = true) + @PreAuthorize("hasAuthority('Basisdaten_DELETE_WLSWahlvorschlaege')") + void deleteAll(); + +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/Wahlvorschlag.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/Wahlvorschlag.java new file mode 100644 index 000000000..eda6a09ee --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/Wahlvorschlag.java @@ -0,0 +1,71 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain; + +import static java.sql.Types.VARCHAR; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.validation.constraints.NotNull; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.UuidGenerator; + +@Entity +@Getter +@Setter +@ToString(onlyExplicitlyIncluded = true) +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +public class Wahlvorschlag { + + @Id + @GeneratedValue(generator = "uuid") + @UuidGenerator + @JdbcTypeCode(VARCHAR) + private UUID id; + + // @Id + @NaturalId + @NotNull + @ToString.Include + private String identifikator; + + @ManyToOne + @NotNull + @JoinColumn(name = "wahlvorschlaegeID") + @EqualsAndHashCode.Exclude + private Wahlvorschlaege wahlvorschlaeage; + + @NotNull + @ToString.Include + private long ordnungszahl; + + @NotNull + private String kurzname; + + @NotNull + @ToString.Include + private boolean erhaeltStimmen; + + @OneToMany(mappedBy = "wahlvorschlag", orphanRemoval = true) + @NotNull + private Set kandidaten = new LinkedHashSet<>(); + + public void addKandidat(final Kandidat kandidat) { + kandidat.setWahlvorschlag(this); + kandidaten.add(kandidat); + } +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/WahlvorschlagRepository.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/WahlvorschlagRepository.java new file mode 100644 index 000000000..c52a21053 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/domain/WahlvorschlagRepository.java @@ -0,0 +1,37 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain; + +import java.util.UUID; +import org.springframework.data.repository.CrudRepository; +import org.springframework.security.access.prepost.PreAuthorize; + +@PreAuthorize("hasAuthority('Basisdaten_READ_Wahlvorschlag')") +public interface WahlvorschlagRepository extends CrudRepository { + + @Override + @PreAuthorize("hasAuthority('Basisdaten_WRITE_Wahlvorschlag')") + S save(S entity); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_WRITE_Wahlvorschlag')") + Iterable saveAll(Iterable entities); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Wahlvorschlag')") + void deleteById(UUID id); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Wahlvorschlag')") + void delete(Wahlvorschlag entity); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Wahlvorschlag')") + void deleteAllById(Iterable uuids); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Wahlvorschlag')") + void deleteAll(Iterable entities); + + @Override + @PreAuthorize("hasAuthority('Basisdaten_DELETE_Wahlvorschlag')") + void deleteAll(); +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/exception/ExceptionConstants.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/exception/ExceptionConstants.java new file mode 100644 index 000000000..17477c786 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/exception/ExceptionConstants.java @@ -0,0 +1,27 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.exception; + +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ExceptionDataWrapper; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ExceptionKonstanten; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ExceptionConstants { + + private static final String CODE_SUCHKRITERIEN_UNVOLLSTAENDIG = "310"; + private static final String MSG_SUCHKRITERIEN_UNVOLLSTAENDIG = "getWahlvorschlaege: Suchkriterien unvollständig."; + + private static final String CODE_UNSAVEABLE = "903"; + private static final String MSG_UNSAVEABLE = "Fehler beim speichern: Daten konnten nicht gespeichert werden."; + + public static ExceptionDataWrapper SUCHKRITERIEN_UNVOLLSTAENDIG = new ExceptionDataWrapper(CODE_SUCHKRITERIEN_UNVOLLSTAENDIG, + MSG_SUCHKRITERIEN_UNVOLLSTAENDIG); + + public static ExceptionDataWrapper UNSAVEABLE = new ExceptionDataWrapper(CODE_UNSAVEABLE, MSG_UNSAVEABLE); + + public static ExceptionDataWrapper NULL_FROM_CLIENT = new ExceptionDataWrapper(ExceptionKonstanten.CODE_ENTITY_NOT_FOUND, "not found"); + + public static ExceptionDataWrapper FAILED_COMMUNICATION_WITH_EAI = new ExceptionDataWrapper("100", + "Bei der Kommunikation mit dem Aoueai-Service ist ein Fehler aufgetreten. Es konnten daher keine Daten geladen werden."); + +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/exception/GlobalExceptionHandler.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..2d26d1ce7 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/exception/GlobalExceptionHandler.java @@ -0,0 +1,33 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.exception; + +import de.muenchen.oss.wahllokalsystem.wls.common.exception.errorhandler.AbstractExceptionHandler; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.rest.model.DTOMapper; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.rest.model.WlsExceptionDTO; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ServiceIDFormatter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +@Slf4j +public class GlobalExceptionHandler extends AbstractExceptionHandler { + + private final ServiceIDFormatter serviceIDFormatter; + + public GlobalExceptionHandler(final ServiceIDFormatter serviceIDFormatter, final DTOMapper dtoMapper) { + super(dtoMapper); + this.serviceIDFormatter = serviceIDFormatter; + } + + @ExceptionHandler + public ResponseEntity handleThrowables(final Throwable throwable) { + log.info("handling throwable", throwable); + return createResponse(getWahlExceptionDTO(throwable)); + } + + @Override + protected String getService() { + return serviceIDFormatter.getId(); + } +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/KandidatDTO.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/KandidatDTO.java new file mode 100644 index 000000000..f71a7b7c5 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/KandidatDTO.java @@ -0,0 +1,11 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.wahlvorschlag; + +import jakarta.validation.constraints.NotNull; + +public record KandidatDTO(@NotNull String identifikator, + @NotNull String name, + @NotNull Long listenposition, + @NotNull Boolean direktkandidat, + @NotNull Long tabellenSpalteInNiederschrift, + @NotNull Boolean einzelbewerber) { +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeController.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeController.java new file mode 100644 index 000000000..94fe3e08d --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeController.java @@ -0,0 +1,40 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeService; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/businessActions/wahlvorschlaege") +@RequiredArgsConstructor +@Slf4j +public class WahlvorschlaegeController { + + private final WahlvorschlaegeService wahlvorschlaegeService; + private final WahlvorschlaegeDTOMapper wahlvorschlaegeDTOMapper; + + @Operation(description = "Laden der Wahlvorschlaege des Wahllokals {wahlbezirkID} für die Wahl {wahlID}.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", description = "OK", + content = { @Content(mediaType = "application/json", schema = @Schema(implementation = WahlvorschlaegeDTO.class)) } + ) + } + ) + @GetMapping("/{wahlID}/{wahlbezirkID}") + public WahlvorschlaegeDTO getWahlvorschlaege(@PathVariable("wahlID") String wahlID, @PathVariable("wahlbezirkID") String wahlbezirkID) { + return wahlvorschlaegeDTOMapper.toDTO( + wahlvorschlaegeService.getWahlvorschlaege(new BezirkUndWahlID(wahlID, wahlbezirkID))); + } +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeDTO.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeDTO.java new file mode 100644 index 000000000..a35e0dbf0 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeDTO.java @@ -0,0 +1,12 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.wahlvorschlag; + +import jakarta.validation.constraints.NotNull; +import java.util.Set; +import lombok.Builder; + +@Builder +public record WahlvorschlaegeDTO(@NotNull String wahlID, + @NotNull String wahlbezirkID, + @NotNull String stimmzettelgebietID, + @NotNull Set wahlvorschlaege) { +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeDTOMapper.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeDTOMapper.java new file mode 100644 index 000000000..d3c41badc --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeDTOMapper.java @@ -0,0 +1,14 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeModel; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface WahlvorschlaegeDTOMapper { + + @Mapping(source = "bezirkUndWahlID.wahlID", target = "wahlID") + @Mapping(source = "bezirkUndWahlID.wahlbezirkID", target = "wahlbezirkID") + WahlvorschlaegeDTO toDTO(WahlvorschlaegeModel wahlvorschlaegeModel); + +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlagDTO.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlagDTO.java new file mode 100644 index 000000000..19ac3af4d --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlagDTO.java @@ -0,0 +1,11 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.wahlvorschlag; + +import jakarta.validation.constraints.NotNull; +import java.util.Set; + +public record WahlvorschlagDTO(@NotNull String identifikator, + @NotNull Long ordnungszahl, + @NotNull String kurzname, + @NotNull Boolean erhaeltStimmen, + Set kandidaten) { +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/KandidatModel.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/KandidatModel.java new file mode 100644 index 000000000..c23adbe54 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/KandidatModel.java @@ -0,0 +1,15 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +@Builder +public record KandidatModel(@NotNull String identifikator, + @NotNull String name, + @NotNull long listenposition, + @NotNull boolean direktkandidat, + @NotNull long tabellenSpalteInNiederschrift, + @NotNull boolean einzelbewerber +) { + +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeClient.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeClient.java new file mode 100644 index 000000000..53f722b62 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeClient.java @@ -0,0 +1,19 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.wls.common.exception.WlsException; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; + +public interface WahlvorschlaegeClient { + + /** + * @param bezirkUndWahlID Reference for requestes Wahlvorschlaege + * @return Model with Wahlvorschlaegen + * @throws WlsException + * {@link de.muenchen.oss.wahllokalsystem.wls.common.exception.FachlicheWlsException} if + * return would be null + * {@link de.muenchen.oss.wahllokalsystem.wls.common.exception.TechnischeWlsException} + * if there were trouble during communication + */ + WahlvorschlaegeModel getWahlvorschlaege(BezirkUndWahlID bezirkUndWahlID) throws WlsException; + +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeModel.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeModel.java new file mode 100644 index 000000000..14279a6fb --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeModel.java @@ -0,0 +1,13 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import jakarta.validation.constraints.NotNull; +import java.util.Set; +import lombok.Builder; + +@Builder +public record WahlvorschlaegeModel(@NotNull BezirkUndWahlID bezirkUndWahlID, + @NotNull String stimmzettelgebietID, + @NotNull Set wahlvorschlaege) { + +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeModelMapper.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeModelMapper.java new file mode 100644 index 000000000..c83d6f444 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeModelMapper.java @@ -0,0 +1,26 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Kandidat; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Wahlvorschlaege; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Wahlvorschlag; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface WahlvorschlaegeModelMapper { + + @Mapping(target = "id", ignore = true) + Wahlvorschlaege toEntity(WahlvorschlaegeModel wahlvorschlaegeModel); + + @Mapping(target = "wahlvorschlaeage", ignore = true) + @Mapping(target = "id", ignore = true) + Wahlvorschlag toEntity(WahlvorschlagModel wahlvorschlagModel); + + @Mapping(target = "wahlvorschlag", ignore = true) + @Mapping(target = "id", ignore = true) + Kandidat toEntity(KandidatModel kandidatModel); + + WahlvorschlaegeModel toModel(Wahlvorschlaege entity); + +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeService.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeService.java new file mode 100644 index 000000000..7726057c0 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeService.java @@ -0,0 +1,62 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.KandidatRepository; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Wahlvorschlaege; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.WahlvorschlaegeRepository; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.WahlvorschlagRepository; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class WahlvorschlaegeService { + + private final WahlvorschlaegeRepository wahlvorschlaegeRepository; + private final WahlvorschlagRepository wahlvorschlagRepository; + private final KandidatRepository kandidatRepository; + private final WahlvorschlaegeModelMapper wahlvorschlaegeModelMapper; + private final WahlvorschlaegeValidator wahlvorschlaegeValidator; + private final WahlvorschlaegeClient wahlvorschlaegeClient; + + @PreAuthorize( + "hasAuthority('Basisdaten_BUSINESSACTION_GetWahlvorschlaege')" + ) + @Transactional + public WahlvorschlaegeModel getWahlvorschlaege(final BezirkUndWahlID bezirkUndWahlID) { + log.debug("#getWahlvorschlaege bezirkUndWahlID > {}", bezirkUndWahlID); + + wahlvorschlaegeValidator.validWahlIdUndWahlbezirkIDOrThrow(bezirkUndWahlID); + val wahlvorschlaegeFromRepo = wahlvorschlaegeRepository.findByBezirkUndWahlID(bezirkUndWahlID); + if (wahlvorschlaegeFromRepo.isEmpty()) { + log.debug("#getWahlvorschlaege: Für BezirkUndWahlID {} waren keine Wahlvorschlaege in der Datenbank", bezirkUndWahlID); + val importedWahlvorschlaegeModel = wahlvorschlaegeClient.getWahlvorschlaege(bezirkUndWahlID); + try { + val savedWahlvorschlaege = persistWahlvorschlagModel(importedWahlvorschlaegeModel); + return wahlvorschlaegeModelMapper.toModel(savedWahlvorschlaege); + } catch (final Exception exception) { + log.error("#getWahlvorschlaege: Fehler beim Cachen", exception); + return importedWahlvorschlaegeModel; // an exception on saving does prevent sending a response. We can use the imported object + } + } else { + return wahlvorschlaegeModelMapper.toModel(wahlvorschlaegeFromRepo.get()); + } + } + + protected Wahlvorschlaege persistWahlvorschlagModel(final WahlvorschlaegeModel wahlvorschlaegeModel) { + val entityToCreate = wahlvorschlaegeModelMapper.toEntity(wahlvorschlaegeModel); + val createdEntity = wahlvorschlaegeRepository.save(entityToCreate); + entityToCreate.getWahlvorschlaege().forEach(wahlvorschlag -> { + wahlvorschlagRepository.save(wahlvorschlag); + kandidatRepository.saveAll(wahlvorschlag.getKandidaten()); + }); + + return createdEntity; + } + +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeValidator.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeValidator.java new file mode 100644 index 000000000..5d2e842a1 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeValidator.java @@ -0,0 +1,21 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.exception.ExceptionConstants; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ExceptionFactory; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class WahlvorschlaegeValidator { + + private final ExceptionFactory exceptionFactory; + + public void validWahlIdUndWahlbezirkIDOrThrow(final BezirkUndWahlID bezirkUndWahlID) { + if (bezirkUndWahlID == null || StringUtils.isEmpty(bezirkUndWahlID.getWahlID()) || StringUtils.isEmpty(bezirkUndWahlID.getWahlbezirkID())) { + throw exceptionFactory.createFachlicheWlsException(ExceptionConstants.SUCHKRITERIEN_UNVOLLSTAENDIG); + } + } +} diff --git a/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlagModel.java b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlagModel.java new file mode 100644 index 000000000..c04fb79f9 --- /dev/null +++ b/wls-basisdaten-service/src/main/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlagModel.java @@ -0,0 +1,16 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import jakarta.validation.constraints.NotNull; +import java.util.Set; +import lombok.Builder; + +@Builder +public record WahlvorschlagModel( + @NotNull String identifikator, + @NotNull Long ordnungszahl, + @NotNull String kurzname, + @NotNull Boolean erhaeltStimmen, + Set kandidaten +) { + +} diff --git a/wls-basisdaten-service/src/main/resources/application.yml b/wls-basisdaten-service/src/main/resources/application.yml index 82eae7f41..a81482fa3 100644 --- a/wls-basisdaten-service/src/main/resources/application.yml +++ b/wls-basisdaten-service/src/main/resources/application.yml @@ -5,6 +5,7 @@ spring: group: local: - db-h2 + - dummy.clients flyway: locations: - classpath:db/migrations/{vendor} @@ -24,6 +25,10 @@ security: oauth2: resource.user-info-uri: http://kubernetes.docker.internal:8100/auth/realms/${realm}/protocol/openid-connect/userinfo +service: + info: + oid: WLS-BASISDATEN + # Define the local keycloak realm here realm: wls_realm diff --git a/wls-basisdaten-service/src/main/resources/db/migrations/h2/V1_0__createWahlvorschlaegeTable.sql b/wls-basisdaten-service/src/main/resources/db/migrations/h2/V1_0__createWahlvorschlaegeTable.sql new file mode 100644 index 000000000..27e7d8ff6 --- /dev/null +++ b/wls-basisdaten-service/src/main/resources/db/migrations/h2/V1_0__createWahlvorschlaegeTable.sql @@ -0,0 +1,45 @@ +CREATE TABLE wahlvorschlaege +( + id VARCHAR(255) NOT NULL, + stimmzettelgebietid VARCHAR(255) NOT NULL, + wahlID VARCHAR(255) NOT NULL, + wahlbezirkID VARCHAR(255) NOT NULL, + + unique (wahlID, wahlbezirkID), + + PRIMARY KEY (id) +); + +CREATE TABLE Wahlvorschlag +( + id VARCHAR(255) NOT NULL, + identifikator VARCHAR(255) NOT NULL, + ordnungszahl BIGINT NOT NULL, + kurzname VARCHAR(255) NOT NULL, + erhaeltStimmen BOOLEAN NOT NULL, + wahlvorschlaegeID VARCHAR(255) NOT NULL, + + unique (identifikator), + + foreign key (wahlvorschlaegeID) REFERENCES wahlvorschlaege (id), + + primary key (id) +); + +CREATE TABLE Kandidat +( + id VARCHAR(255) NOT NULL, + identifikator VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL, + listenposition BIGINT NOT NULL, + direktkandidat BOOLEAN NOT NULL, + tabellenSpalteInNiederschrift BIGINT NOT NULL, + einzelbewerber BOOLEAN NOT NULL, + wahlvorschlagID VARCHAR(255) NOT NULL, + + unique (identifikator), + + foreign key (wahlvorschlagID) REFERENCES Wahlvorschlag (id), + + primary key (id) +) \ No newline at end of file diff --git a/wls-basisdaten-service/src/main/resources/db/migrations/oracle/V1_0__createWahlvorschlaegeTable.sql b/wls-basisdaten-service/src/main/resources/db/migrations/oracle/V1_0__createWahlvorschlaegeTable.sql new file mode 100644 index 000000000..6cc9e204c --- /dev/null +++ b/wls-basisdaten-service/src/main/resources/db/migrations/oracle/V1_0__createWahlvorschlaegeTable.sql @@ -0,0 +1,45 @@ +CREATE TABLE wahlvorschlaege +( + id VARCHAR(255) NOT NULL, + stimmzettelgebietid VARCHAR(255) NOT NULL, + wahlID VARCHAR(255) NOT NULL, + wahlbezirkID VARCHAR(255) NOT NULL, + + unique (wahlID, wahlbezirkID), + + PRIMARY KEY (id) +); + +CREATE TABLE Wahlvorschlag +( + id VARCHAR(255) NOT NULL, + identifikator VARCHAR(255) NOT NULL, + ordnungszahl NUMBER NOT NULL, + kurzname VARCHAR(255) NOT NULL, + erhaeltStimmen NUMBER NOT NULL, + wahlvorschlaegeID VARCHAR(255) NOT NULL, + + unique (identifikator), + + foreign key (wahlvorschlaegeID) REFERENCES wahlvorschlaege (id), + + primary key (id) +); + +CREATE TABLE Kandidat +( + id VARCHAR(255) NOT NULL, + identifikator VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL, + listenposition NUMBER NOT NULL, + direktkandidat NUMBER NOT NULL, + tabellenSpalteInNiederschrift NUMBER NOT NULL, + einzelbewerber NUMBER NOT NULL, + wahlvorschlagID VARCHAR(255) NOT NULL, + + unique (identifikator), + + foreign key (wahlvorschlagID) REFERENCES Wahlvorschlag (id), + + primary key (id) +) \ No newline at end of file diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/ClientImplTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/ClientImplTest.java new file mode 100644 index 000000000..fa4982b75 --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/ClientImplTest.java @@ -0,0 +1,68 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.clients; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.client.WahlvorschlagControllerApi; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlaegeDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.exception.ExceptionConstants; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeModel; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.FachlicheWlsException; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ExceptionFactory; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ClientImplTest { + + @Mock + ExceptionFactory exceptionFactory; + + @Mock + WahlvorschlagControllerApi wahlvorschlagControllerApi; + + @Mock + WahlvorschlaegeClientMapper wahlvorschlaegeClientMapper; + + @InjectMocks + ClientImpl unitUnderTest; + + @Nested + class GetWahlvorschlaege { + + @Test + void clientResponseIsMapped() { + val bezirkUndWahlID = new BezirkUndWahlID("wahlID", "wahlbezirkID"); + + val mockedClientResponse = new WahlvorschlaegeDTO(); + val mockedMappedClientResponse = WahlvorschlaegeModel.builder().build(); + + Mockito.when(wahlvorschlagControllerApi.loadWahlvorschlaege(eq(bezirkUndWahlID.getWahlID()), eq(bezirkUndWahlID.getWahlbezirkID()))) + .thenReturn(mockedClientResponse); + Mockito.when(wahlvorschlaegeClientMapper.toModel(mockedClientResponse)).thenReturn(mockedMappedClientResponse); + + val result = unitUnderTest.getWahlvorschlaege(bezirkUndWahlID); + + Assertions.assertThat(result).isSameAs(mockedMappedClientResponse); + } + + @Test + void exceptionWhenClientResponseIsNull() { + val mockedWlsException = FachlicheWlsException.withCode("").buildWithMessage(""); + + Mockito.when(wahlvorschlagControllerApi.loadWahlvorschlaege(any(), any())).thenReturn(null); + Mockito.when(exceptionFactory.createFachlicheWlsException(ExceptionConstants.NULL_FROM_CLIENT)).thenReturn(mockedWlsException); + + Assertions.assertThatException().isThrownBy(() -> unitUnderTest.getWahlvorschlaege(new BezirkUndWahlID("", ""))).isSameAs(mockedWlsException); + } + } + +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/DummyClientImplTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/DummyClientImplTest.java new file mode 100644 index 000000000..b39c42dd2 --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/DummyClientImplTest.java @@ -0,0 +1,23 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.clients; + +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class DummyClientImplTest { + + private final DummyClientImpl unitUnderTest = new DummyClientImpl(); + + @Nested + class GetWahlvorschlaege { + + @Test + void resultIsANonNullObject() { + val result = unitUnderTest.getWahlvorschlaege(new BezirkUndWahlID("wahlID", "wahlbezirkID")); + + Assertions.assertThat(result).hasNoNullFieldsOrProperties(); + } + } +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/WahlvorschlaegeClientMapperTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/WahlvorschlaegeClientMapperTest.java new file mode 100644 index 000000000..23e590515 --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/clients/WahlvorschlaegeClientMapperTest.java @@ -0,0 +1,98 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.clients; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.KandidatDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlaegeDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlagDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.KandidatModel; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeModel; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlagModel; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import java.util.Set; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +class WahlvorschlaegeClientMapperTest { + + private final WahlvorschlaegeClientMapper unitUnderTest = Mappers.getMapper(WahlvorschlaegeClientMapper.class); + + @Nested + class ToModel { + + @Test + void isMapped() { + val stimmzettelgebietID = "stimmzettelgebietID"; + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + + val dtoToMap = new WahlvorschlaegeDTO(); + dtoToMap.setStimmzettelgebietID(stimmzettelgebietID); + dtoToMap.setWahlID(wahlID); + dtoToMap.setWahlbezirkID(wahlbezirkID); + + val wahlvorschlag1 = new WahlvorschlagDTO(); + wahlvorschlag1.setErhaeltStimmen(true); + wahlvorschlag1.setIdentifikator("identifikator1"); + wahlvorschlag1.setKurzname("kurzname1"); + wahlvorschlag1.setOrdnungszahl(1L); + + val kandidat11 = new KandidatDTO(); + kandidat11.setIdentifikator("kandidat11"); + kandidat11.setDirektkandidat(true); + kandidat11.setEinzelbewerber(true); + kandidat11.setName("name11"); + kandidat11.setListenposition(1L); + kandidat11.setTabellenSpalteInNiederschrift(1L); + + val kandidat12 = new KandidatDTO(); + kandidat12.setIdentifikator("kandidat12"); + kandidat12.setDirektkandidat(false); + kandidat12.setEinzelbewerber(false); + kandidat12.setName("name12"); + kandidat12.setListenposition(2L); + kandidat12.setTabellenSpalteInNiederschrift(2L); + wahlvorschlag1.setKandidaten(Set.of(kandidat11, kandidat12)); + + val wahlvorschlag2 = new WahlvorschlagDTO(); + wahlvorschlag2.setErhaeltStimmen(false); + wahlvorschlag2.setIdentifikator("identifikator2"); + wahlvorschlag2.setKurzname("kurzname2"); + wahlvorschlag2.setOrdnungszahl(2L); + + val kandidat21 = new KandidatDTO(); + kandidat21.setIdentifikator("kandidat21"); + kandidat21.setDirektkandidat(true); + kandidat21.setEinzelbewerber(true); + kandidat21.setName("name21"); + kandidat21.setListenposition(3L); + kandidat21.setTabellenSpalteInNiederschrift(3L); + + val kandidat22 = new KandidatDTO(); + kandidat22.setIdentifikator("kandidat22"); + kandidat22.setDirektkandidat(false); + kandidat22.setEinzelbewerber(false); + kandidat22.setName("name22"); + kandidat22.setListenposition(4L); + kandidat22.setTabellenSpalteInNiederschrift(4L); + wahlvorschlag2.setKandidaten(Set.of(kandidat21, kandidat22)); + + val wahlvorschlaege = Set.of(wahlvorschlag1, wahlvorschlag2); + dtoToMap.setWahlvorschlaege(wahlvorschlaege); + Assertions.assertThat(dtoToMap).hasNoNullFieldsOrProperties(); + + val result = unitUnderTest.toModel(dtoToMap); + + val expectedWahlvorschlaege = Set.of(new WahlvorschlagModel("identifikator1", 1L, "kurzname1", true, Set.of( + new KandidatModel("kandidat11", "name11", 1L, true, 1L, true), + new KandidatModel("kandidat12", "name12", 2L, false, 2L, false))), + new WahlvorschlagModel("identifikator2", 2L, "kurzname2", false, Set.of( + new KandidatModel("kandidat21", "name21", 3L, true, 3L, true), + new KandidatModel("kandidat22", "name22", 4L, false, 4L, false)))); + val expectedResult = new WahlvorschlaegeModel(new BezirkUndWahlID(wahlID, wahlbezirkID), stimmzettelgebietID, expectedWahlvorschlaege); + + Assertions.assertThat(result).usingRecursiveComparison().isEqualTo(expectedResult); + } + } +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/configuration/SecurityConfigurationTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/configuration/SecurityConfigurationTest.java index 5facfa0fd..2aa68c8cd 100644 --- a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/configuration/SecurityConfigurationTest.java +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/configuration/SecurityConfigurationTest.java @@ -5,11 +5,16 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import de.muenchen.oss.wahllokalsystem.basisdatenservice.MicroServiceApplication; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeService; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithAnonymousUser; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; @@ -22,6 +27,9 @@ class SecurityConfigurationTest { @Autowired MockMvc api; + @MockBean + WahlvorschlaegeService wahlvorschlaegeService; + @Test void accessSecuredResourceRootThenUnauthorized() throws Exception { api.perform(get("/")) @@ -64,4 +72,20 @@ void accessUnsecuredResourceSwaggerUiThenOk() throws Exception { .andExpect(status().isOk()); } + @Nested + class Wahlvorschlaege { + + @Test + @WithAnonymousUser + void accessGetWahlvorstaendeUnauthorizedThenUnauthorized() throws Exception { + api.perform(get("/businessActions/wahlvorschlaege/wahlID/wahlbezirkID")).andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void accessGetWahlvorstaendeUnauthorizedThenOk() throws Exception { + api.perform(get("/businessActions/wahlvorschlaege/wahlID/wahlbezirkID")).andExpect(status().isOk()); + } + } + } diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeControllerIntegrationTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeControllerIntegrationTest.java new file mode 100644 index 000000000..4cf19583b --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeControllerIntegrationTest.java @@ -0,0 +1,243 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.wahlvorschlag; + +import static de.muenchen.oss.wahllokalsystem.basisdatenservice.TestConstants.SPRING_NO_SECURITY_PROFILE; +import static de.muenchen.oss.wahllokalsystem.basisdatenservice.TestConstants.SPRING_TEST_PROFILE; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.MicroServiceApplication; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.clients.WahlvorschlaegeClientMapper; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.KandidatRepository; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.WahlvorschlaegeRepository; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.WahlvorschlagRepository; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.KandidatDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlagDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.exception.ExceptionConstants; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeModelMapper; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeValidator; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.utils.Authorities; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.rest.model.WlsExceptionCategory; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.rest.model.WlsExceptionDTO; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import de.muenchen.oss.wahllokalsystem.wls.common.testing.SecurityUtils; +import java.util.Set; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest(classes = MicroServiceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.MOCK) +@AutoConfigureMockMvc +@AutoConfigureWireMock +@ActiveProfiles(profiles = { SPRING_TEST_PROFILE, SPRING_NO_SECURITY_PROFILE }) +public class WahlvorschlaegeControllerIntegrationTest { + + @Value("${service.info.oid}") + String serviceID; + + @Autowired + MockMvc api; + + @Autowired + ObjectMapper objectMapper; + + @Autowired + WahlvorschlaegeDTOMapper dtoMapper; + + @Autowired + WahlvorschlaegeModelMapper modelMapper; + + @Autowired + WahlvorschlaegeClientMapper wahlvorschlaegeClientMapper; + + @Autowired + WahlvorschlaegeRepository wahlvorschlaegeRepository; + + @Autowired + WahlvorschlagRepository wahlvorschlagRepository; + + @Autowired + KandidatRepository kandidatRepository; + + @SpyBean + WahlvorschlaegeValidator wahlvorschlaegeValidator; + + @AfterEach + void tearDown() { + SecurityUtils.runWith(Authorities.ALL_AUTHORITIES_DELETE_WAHLVORSCHLAEGE); + wahlvorschlaegeRepository.deleteAll(); + } + + @BeforeEach + void setup() { + WireMock.resetAllRequests(); + } + + @Nested + class GetWahlvorschlaege { + + @Test + void loadedFromExternal() throws Exception { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + + val eaiWahlvorschlaege = createClientWahlvorschlaegeDTO(wahlID, wahlbezirkID); + WireMock.stubFor(WireMock.get("/vorschlaege/wahl/" + wahlID + "/" + wahlbezirkID) + .willReturn(WireMock.aResponse().withHeader("Content-Type", "application/json").withStatus(HttpStatus.OK.value()) + .withBody(objectMapper.writeValueAsBytes(eaiWahlvorschlaege)))); + + val request = MockMvcRequestBuilders.get("/businessActions/wahlvorschlaege/" + wahlID + "/" + wahlbezirkID); + + val response = api.perform(request).andExpect(status().isOk()).andReturn(); + val responseBodyAsDTO = objectMapper.readValue(response.getResponse().getContentAsString(), WahlvorschlaegeDTO.class); + + val expectedResponseBody = dtoMapper.toDTO(wahlvorschlaegeClientMapper.toModel(eaiWahlvorschlaege)); + + Assertions.assertThat(responseBodyAsDTO).isEqualTo(expectedResponseBody); + } + + @Test + @Transactional + void externalDataIsPersisted() throws Exception { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + + val eaiWahlvorschlaege = createClientWahlvorschlaegeDTO(wahlID, wahlbezirkID); + WireMock.stubFor(WireMock.get("/vorschlaege/wahl/" + wahlID + "/" + wahlbezirkID) + .willReturn(WireMock.aResponse().withHeader("Content-Type", "application/json").withStatus(HttpStatus.OK.value()) + .withBody(objectMapper.writeValueAsBytes(eaiWahlvorschlaege)))); + + val request = MockMvcRequestBuilders.get("/businessActions/wahlvorschlaege/" + wahlID + "/" + wahlbezirkID); + + api.perform(request).andExpect(status().isOk()); + + val dataFromRepo = wahlvorschlaegeRepository.findByBezirkUndWahlID(new BezirkUndWahlID(wahlID, wahlbezirkID)).get(); + + val expectedEntity = modelMapper.toEntity(wahlvorschlaegeClientMapper.toModel(eaiWahlvorschlaege)); + + Assertions.assertThat(dataFromRepo).usingRecursiveComparison().ignoringCollectionOrder() + .ignoringFields("id", "wahlvorschlaege.id", "wahlvorschlaege.wahlvorschlaeage", + "wahlvorschlaege.kandidaten.id", "wahlvorschlaege.kandidaten.wahlvorschlag") + .isEqualTo(expectedEntity); + } + + @Test + void loadFromRepository() throws Exception { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + + val entityToFind = modelMapper.toEntity( + wahlvorschlaegeClientMapper.toModel(createClientWahlvorschlaegeDTO(wahlID, wahlbezirkID))); + val savedEntity = wahlvorschlaegeRepository.save(entityToFind); + entityToFind.getWahlvorschlaege().forEach(wahlvorschlag -> { + wahlvorschlagRepository.save(wahlvorschlag); + kandidatRepository.saveAll(wahlvorschlag.getKandidaten()); + }); + + val request = MockMvcRequestBuilders.get("/businessActions/wahlvorschlaege/" + wahlID + "/" + wahlbezirkID); + + val response = api.perform(request).andExpect(status().isOk()).andReturn(); + val responseBodyAsDTO = objectMapper.readValue(response.getResponse().getContentAsString(), WahlvorschlaegeDTO.class); + + val expectedResponseBody = dtoMapper.toDTO(modelMapper.toModel(savedEntity)); + + Assertions.assertThat(responseBodyAsDTO).isEqualTo(expectedResponseBody); + WireMock.verify(0, WireMock.anyRequestedFor(WireMock.anyUrl())); + } + + @Test + void technischeWlsExceptionWhenNoExternalDataFound() throws Exception { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + + WireMock.stubFor(WireMock.get("/vorschlaege/wahl/" + wahlID + "/" + wahlbezirkID) + .willReturn(WireMock.aResponse().withHeader("Content-Type", "application/json").withStatus(HttpStatus.NOT_FOUND.value()))); + + val request = MockMvcRequestBuilders.get("/businessActions/wahlvorschlaege/" + wahlID + "/" + wahlbezirkID); + + val response = api.perform(request).andExpect(status().isInternalServerError()).andReturn(); + val responseBodyAsWlsExceptionDTO = objectMapper.readValue(response.getResponse().getContentAsString(), WlsExceptionDTO.class); + + val expectedWlsExceptionDTO = new WlsExceptionDTO(WlsExceptionCategory.T, + ExceptionConstants.FAILED_COMMUNICATION_WITH_EAI.code(), serviceID, + ExceptionConstants.FAILED_COMMUNICATION_WITH_EAI.message()); + Assertions.assertThat(responseBodyAsWlsExceptionDTO).isEqualTo(expectedWlsExceptionDTO); + } + } + + private de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlaegeDTO createClientWahlvorschlaegeDTO(final String wahlID, + final String wahlbezirkID) { + val stimmzettelgebietID = "stimmzettelgebietID"; + + val clientWahlvorschlaegeDTO = new de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlaegeDTO(); + clientWahlvorschlaegeDTO.setStimmzettelgebietID(stimmzettelgebietID); + clientWahlvorschlaegeDTO.setWahlID(wahlID); + clientWahlvorschlaegeDTO.setWahlbezirkID(wahlbezirkID); + + val wahlvorschlag1 = new de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlagDTO(); + wahlvorschlag1.setErhaeltStimmen(true); + wahlvorschlag1.setIdentifikator("identifikator1"); + wahlvorschlag1.setKurzname("kurzname1"); + wahlvorschlag1.setOrdnungszahl(1L); + + val kandidat11 = new de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.KandidatDTO(); + kandidat11.setIdentifikator("kandidat11"); + kandidat11.setDirektkandidat(true); + kandidat11.setEinzelbewerber(true); + kandidat11.setName("name11"); + kandidat11.setListenposition(1L); + kandidat11.setTabellenSpalteInNiederschrift(1L); + + val kandidat12 = new de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.KandidatDTO(); + kandidat12.setIdentifikator("kandidat12"); + kandidat12.setDirektkandidat(false); + kandidat12.setEinzelbewerber(false); + kandidat12.setName("name12"); + kandidat12.setListenposition(2L); + kandidat12.setTabellenSpalteInNiederschrift(2L); + wahlvorschlag1.setKandidaten(Set.of(kandidat11, kandidat12)); + + val wahlvorschlag2 = new WahlvorschlagDTO(); + wahlvorschlag2.setErhaeltStimmen(false); + wahlvorschlag2.setIdentifikator("identifikator2"); + wahlvorschlag2.setKurzname("kurzname2"); + wahlvorschlag2.setOrdnungszahl(2L); + + val kandidat21 = new de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.KandidatDTO(); + kandidat21.setIdentifikator("kandidat21"); + kandidat21.setDirektkandidat(true); + kandidat21.setEinzelbewerber(true); + kandidat21.setName("name21"); + kandidat21.setListenposition(3L); + kandidat21.setTabellenSpalteInNiederschrift(3L); + + val kandidat22 = new KandidatDTO(); + kandidat22.setIdentifikator("kandidat22"); + kandidat22.setDirektkandidat(false); + kandidat22.setEinzelbewerber(false); + kandidat22.setName("name22"); + kandidat22.setListenposition(4L); + kandidat22.setTabellenSpalteInNiederschrift(4L); + wahlvorschlag2.setKandidaten(Set.of(kandidat21, kandidat22)); + + val wahlvorschlaege = Set.of(wahlvorschlag1, wahlvorschlag2); + clientWahlvorschlaegeDTO.setWahlvorschlaege(wahlvorschlaege); + + return clientWahlvorschlaegeDTO; + } + +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeControllerTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeControllerTest.java new file mode 100644 index 000000000..d0c6bdfcb --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeControllerTest.java @@ -0,0 +1,47 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeModel; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeService; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class WahlvorschlaegeControllerTest { + + @Mock + WahlvorschlaegeService wahlvorschlaegeService; + + @Mock + WahlvorschlaegeDTOMapper wahlvorschlaegeDTOMapper; + + @InjectMocks + WahlvorschlaegeController wahlvorschlaegeController; + + @Nested + class GetWahlvorschlaege { + + @Test + void serviceIsCalledAndObjectsAreMapped() { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + + val wahlvorschlaegeModel = WahlvorschlaegeModel.builder().build(); + val wahlvorschlaegeDTO = WahlvorschlaegeDTO.builder().build(); + + Mockito.when(wahlvorschlaegeService.getWahlvorschlaege(new BezirkUndWahlID(wahlID, wahlbezirkID))).thenReturn(wahlvorschlaegeModel); + Mockito.when(wahlvorschlaegeDTOMapper.toDTO(wahlvorschlaegeModel)).thenReturn(wahlvorschlaegeDTO); + + val result = wahlvorschlaegeController.getWahlvorschlaege(wahlID, wahlbezirkID); + + Assertions.assertThat(result).isEqualTo(wahlvorschlaegeDTO); + } + } +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeDTOMapperTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeDTOMapperTest.java new file mode 100644 index 000000000..37766bc5b --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/rest/wahlvorschlag/WahlvorschlaegeDTOMapperTest.java @@ -0,0 +1,60 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.KandidatModel; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlaegeModel; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag.WahlvorschlagModel; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import java.util.Set; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +class WahlvorschlaegeDTOMapperTest { + + private final WahlvorschlaegeDTOMapper unitUnderTest = Mappers.getMapper(WahlvorschlaegeDTOMapper.class); + + @Nested + class FromWahlvorschlagModelToWLSDTO { + + @Test + void nullInNullOut() { + Assertions.assertThat(unitUnderTest.toDTO(null)).isNull(); + } + + @Test + void isMappedToDTO() { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + + val modelInput = createWahlvorschlaegeModel(wahlID, wahlbezirkID); + val dtoExpected = createWahlvorschlaegeDTO(wahlID, wahlbezirkID); + + val result = unitUnderTest.toDTO(modelInput); + Assertions.assertThat(result).isEqualTo(dtoExpected); + } + + private WahlvorschlaegeDTO createWahlvorschlaegeDTO(final String wahlID, final String wahlbezirkID) { + return new WahlvorschlaegeDTO(wahlID, wahlbezirkID, "stimmzettelgebietID", + Set.of( + new WahlvorschlagDTO("id1", 1L, "kurzname1", true, Set.of( + new KandidatDTO("kandidatID1", "name1", 1L, true, 1L, true), + new KandidatDTO("kandidatID2", "name2", 2L, false, 2L, false))), + new WahlvorschlagDTO("id2", 2L, "kurzname2", false, Set.of( + new KandidatDTO("kandidatID3", "name3", 1L, true, 1L, true), + new KandidatDTO("kandidatID4", "name4", 2L, false, 2L, false))))); + } + + private WahlvorschlaegeModel createWahlvorschlaegeModel(final String wahlID, final String wahlbezirkID) { + return new WahlvorschlaegeModel(new BezirkUndWahlID(wahlID, wahlbezirkID), "stimmzettelgebietID", + Set.of( + new WahlvorschlagModel("id1", 1L, "kurzname1", true, Set.of( + new KandidatModel("kandidatID1", "name1", 1L, true, 1L, true), + new KandidatModel("kandidatID2", "name2", 2L, false, 2L, false))), + new WahlvorschlagModel("id2", 2L, "kurzname2", false, Set.of( + new KandidatModel("kandidatID3", "name3", 1L, true, 1L, true), + new KandidatModel("kandidatID4", "name4", 2L, false, 2L, false))))); + } + } +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeModelMapperTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeModelMapperTest.java new file mode 100644 index 000000000..622f86bb5 --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeModelMapperTest.java @@ -0,0 +1,66 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Kandidat; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Wahlvorschlaege; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Wahlvorschlag; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import java.util.Set; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +class WahlvorschlaegeModelMapperTest { + + private final WahlvorschlaegeModelMapper unitUnderTest = Mappers.getMapper(WahlvorschlaegeModelMapper.class); + + @Test + void modelIsMappedToEntity() { + val modelToMap = createWahlvorschlaegeModel(); + + val result = unitUnderTest.toEntity(modelToMap); + + val expectedResult = createWahlvorschlaegeEntity(); + Assertions.assertThat(result).isEqualTo(expectedResult); + } + + @Test + void entityIsMappedToModel() { + val entityToMap = createWahlvorschlaegeEntity(); + + val result = unitUnderTest.toModel(entityToMap); + val expectedResult = createWahlvorschlaegeModel(); + + Assertions.assertThat(result).isEqualTo(expectedResult); + } + + private Wahlvorschlaege createWahlvorschlaegeEntity() { + val entity = new Wahlvorschlaege(null, new BezirkUndWahlID("wahlID", "wahlbezirkID"), "stimmzettelgebietID", + null); + val wahlvorschlag1 = new Wahlvorschlag(null, "id1", entity, 1L, "kurzname1", true, null); + val kandidat1 = new Kandidat(null, "kandidatID1", wahlvorschlag1, "name1", 1L, true, 1L, true); + val kandidat2 = new Kandidat(null, "kandidatID2", wahlvorschlag1, "name2", 2L, false, 2L, false); + wahlvorschlag1.setKandidaten(Set.of(kandidat1, kandidat2)); + + val wahlvorschlag2 = new Wahlvorschlag(null, "id2", entity, 2L, "kurzname2", false, null); + val kandidat3 = new Kandidat(null, "kandidatID3", wahlvorschlag2, "name3", 1L, true, 1L, true); + val kandidat4 = new Kandidat(null, "kandidatID4", wahlvorschlag2, "name4", 2L, false, 2L, false); + wahlvorschlag2.setKandidaten(Set.of(kandidat3, kandidat4)); + + entity.setWahlvorschlaege(Set.of(wahlvorschlag1, wahlvorschlag2)); + + return entity; + } + + private WahlvorschlaegeModel createWahlvorschlaegeModel() { + return new WahlvorschlaegeModel(new BezirkUndWahlID("wahlID", "wahlbezirkID"), "stimmzettelgebietID", + Set.of( + new WahlvorschlagModel("id1", 1L, "kurzname1", true, Set.of( + new KandidatModel("kandidatID1", "name1", 1L, true, 1L, true), + new KandidatModel("kandidatID2", "name2", 2L, false, 2L, false))), + new WahlvorschlagModel("id2", 2L, "kurzname2", false, Set.of( + new KandidatModel("kandidatID3", "name3", 1L, true, 1L, true), + new KandidatModel("kandidatID4", "name4", 2L, false, 2L, false))))); + } + +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeServiceSecurityTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeServiceSecurityTest.java new file mode 100644 index 000000000..9cfc5ad02 --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeServiceSecurityTest.java @@ -0,0 +1,117 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.MicroServiceApplication; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.TestConstants; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.WahlvorschlaegeRepository; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.KandidatDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlaegeDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.eai.aou.model.WahlvorschlagDTO; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.utils.Authorities; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import de.muenchen.oss.wahllokalsystem.wls.common.testing.SecurityUtils; +import java.util.Set; +import java.util.stream.Stream; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest(classes = MicroServiceApplication.class) +@ActiveProfiles(TestConstants.SPRING_TEST_PROFILE) +@AutoConfigureWireMock +public class WahlvorschlaegeServiceSecurityTest { + + @Autowired + WahlvorschlaegeService wahlvorschlaegeService; + + @Autowired + WahlvorschlaegeRepository wahlvorschlaegeRepository; + + @Autowired + ObjectMapper objectMapper; + + @Nested + class GetWahlvorschlaege { + + @AfterEach + void tearDown() { + SecurityUtils.runWith(Authorities.ALL_AUTHORITIES_DELETE_WAHLVORSCHLAEGE); + wahlvorschlaegeRepository.deleteAll(); + } + + @Test + void accessGranted() throws Exception { + SecurityUtils.runWith(Authorities.ALL_AUTHORITIES_GET_WAHLVORSCHLAEGE); + + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + + val eaiWahlvorschlaege = createClientWahlvorschlaegeDTO(); + WireMock.stubFor(WireMock.get("/vorschlaege/wahl/" + wahlID + "/" + wahlbezirkID) + .willReturn(WireMock.aResponse().withHeader("Content-Type", "application/json").withStatus(HttpStatus.OK.value()) + .withBody(objectMapper.writeValueAsBytes(eaiWahlvorschlaege)))); + + Assertions.assertThatNoException().isThrownBy(() -> wahlvorschlaegeService.getWahlvorschlaege(new BezirkUndWahlID(wahlID, wahlbezirkID))); + } + + @ParameterizedTest(name = "{index} - {1} missing") + @MethodSource("getMissingAuthoritiesVariations") + void missingAuthorityCausesFailWithAccessDenied(final ArgumentsAccessor argumentsAccessor) throws Exception { + SecurityUtils.runWith(argumentsAccessor.get(0, String[].class)); + + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + + val eaiWahlvorschlaege = createClientWahlvorschlaegeDTO(); + WireMock.stubFor(WireMock.get("/vorschlaege/wahl/" + wahlID + "/" + wahlbezirkID) + .willReturn(WireMock.aResponse().withHeader("Content-Type", "application/json").withStatus(HttpStatus.OK.value()) + .withBody(objectMapper.writeValueAsBytes(eaiWahlvorschlaege)))); + + Assertions.assertThatException().isThrownBy(() -> wahlvorschlaegeService.getWahlvorschlaege(new BezirkUndWahlID(wahlID, wahlbezirkID))) + .isInstanceOf( + AccessDeniedException.class); + } + + private static Stream getMissingAuthoritiesVariations() { + return SecurityUtils.buildArgumentsForMissingAuthoritiesVariations(Authorities.ALL_AUTHORITIES_DELETE_WAHLVORSCHLAEGE); + } + + private WahlvorschlaegeDTO createClientWahlvorschlaegeDTO() { + val dto = new WahlvorschlaegeDTO(); + + dto.setStimmzettelgebietID("stimmzettelgebietID"); + dto.setWahlbezirkID("wahlbezirkID"); + dto.setWahlID("wahlID"); + + val wahlvorschlag1 = new WahlvorschlagDTO(); + wahlvorschlag1.setIdentifikator("wahlvorschlagID"); + wahlvorschlag1.setKurzname("kurzname"); + wahlvorschlag1.setOrdnungszahl(1L); + wahlvorschlag1.setErhaeltStimmen(true); + + val kandidat1 = new KandidatDTO(); + kandidat1.setName("kandidat"); + kandidat1.setListenposition(1L); + kandidat1.setEinzelbewerber(true); + kandidat1.setDirektkandidat(true); + kandidat1.setIdentifikator("kandidatID"); + wahlvorschlag1.setKandidaten(Set.of(kandidat1)); + dto.setWahlvorschlaege(Set.of(wahlvorschlag1)); + + return dto; + } + } +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeServiceTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeServiceTest.java new file mode 100644 index 000000000..d75e67f05 --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeServiceTest.java @@ -0,0 +1,156 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Kandidat; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.KandidatRepository; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Wahlvorschlaege; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.WahlvorschlaegeRepository; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Wahlvorschlag; +import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.WahlvorschlagRepository; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import lombok.val; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class WahlvorschlaegeServiceTest { + + @Mock + WahlvorschlaegeRepository wahlvorschlaegeRepository; + @Mock + WahlvorschlagRepository wahlvorschlagRepository; + @Mock + KandidatRepository kandidatRepository; + @Mock + WahlvorschlaegeModelMapper wahlvorschlaegeModelMapper; + @Mock + WahlvorschlaegeValidator wahlvorschlaegeValidator; + @Mock + WahlvorschlaegeClient wahlvorschlaegeClient; + + @InjectMocks + WahlvorschlaegeService unitUnderTest; + + @Nested + class GetWahlvorschlaege { + + @Test + void missingDataIsLoadedAndStored() { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + val wahlUndBezirkID = new BezirkUndWahlID(wahlID, wahlbezirkID); + + val mockedClientResponse = WahlvorschlaegeModel.builder().build(); + val mockedWahlvorschlagEntity1 = new Wahlvorschlag(); + + val mockedKandidatEntity1 = new Kandidat(); + mockedKandidatEntity1.setId(UUID.randomUUID()); + val mockedKandidatEntity2 = new Kandidat(); + mockedKandidatEntity2.setId(UUID.randomUUID()); + mockedWahlvorschlagEntity1.setKandidaten(Set.of(mockedKandidatEntity1, mockedKandidatEntity2)); + val mockedWahlvorschlagEntity2 = new Wahlvorschlag(); + + val mockedKandidatEntity3 = new Kandidat(); + mockedKandidatEntity3.setId(UUID.randomUUID()); + val mockedKandidatEntity4 = new Kandidat(); + mockedKandidatEntity4.setId(UUID.randomUUID()); + mockedWahlvorschlagEntity2.setKandidaten(Set.of(mockedKandidatEntity3, mockedKandidatEntity4)); + val mockedMappedEntity = new Wahlvorschlaege(); + + mockedMappedEntity.setWahlvorschlaege(Set.of(mockedWahlvorschlagEntity1, mockedWahlvorschlagEntity2)); + + val mockedMappedSavedEntity = WahlvorschlaegeModel.builder().build(); + + Mockito.when(wahlvorschlaegeRepository.findByBezirkUndWahlID(wahlUndBezirkID)).thenReturn(Optional.empty()); + Mockito.when(wahlvorschlaegeRepository.save(mockedMappedEntity)).thenReturn(mockedMappedEntity); + Mockito.when(wahlvorschlaegeClient.getWahlvorschlaege(wahlUndBezirkID)).thenReturn(mockedClientResponse); + Mockito.when(wahlvorschlaegeModelMapper.toEntity(mockedClientResponse)).thenReturn(mockedMappedEntity); + Mockito.when(wahlvorschlaegeModelMapper.toModel(mockedMappedEntity)).thenReturn(mockedMappedSavedEntity); + + val result = unitUnderTest.getWahlvorschlaege(wahlUndBezirkID); + + Assertions.assertThat(result).isSameAs(mockedMappedSavedEntity); + Mockito.verify(wahlvorschlaegeRepository).save(mockedMappedEntity); + Mockito.verify(wahlvorschlagRepository).save(mockedWahlvorschlagEntity1); + Mockito.verify(wahlvorschlagRepository).save(mockedWahlvorschlagEntity2); + Mockito.verify(kandidatRepository).saveAll(mockedWahlvorschlagEntity1.getKandidaten()); + Mockito.verify(kandidatRepository).saveAll(mockedWahlvorschlagEntity2.getKandidaten()); + } + + @Test + void existingDataIsLoaded() { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + val wahlUndBezirkID = new BezirkUndWahlID(wahlID, wahlbezirkID); + + val mockedWahlvorschlaegeFromRepo = new Wahlvorschlaege(); + val mockedMappedWahlvorschlaegeAsModel = WahlvorschlaegeModel.builder().build(); + + Mockito.when(wahlvorschlaegeRepository.findByBezirkUndWahlID(wahlUndBezirkID)).thenReturn(Optional.of(mockedWahlvorschlaegeFromRepo)); + Mockito.when(wahlvorschlaegeModelMapper.toModel(mockedWahlvorschlaegeFromRepo)).thenReturn(mockedMappedWahlvorschlaegeAsModel); + + val result = unitUnderTest.getWahlvorschlaege(wahlUndBezirkID); + + Assertions.assertThat(result).isSameAs(mockedMappedWahlvorschlaegeAsModel); + Mockito.verify(wahlvorschlaegeClient, times(0)).getWahlvorschlaege(any()); + } + + @Test + void validationExceptionGotThrown() { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + val wahlUndBezirkID = new BezirkUndWahlID(wahlID, wahlbezirkID); + + val mockedValidationException = new RuntimeException("validation failed"); + + Mockito.doThrow(mockedValidationException).when(wahlvorschlaegeValidator).validWahlIdUndWahlbezirkIDOrThrow(wahlUndBezirkID); + + Assertions.assertThatException().isThrownBy(() -> unitUnderTest.getWahlvorschlaege(wahlUndBezirkID)).isSameAs(mockedValidationException); + } + + @Test + void clientExceptionGotThrown() { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + val wahlUndBezirkID = new BezirkUndWahlID(wahlID, wahlbezirkID); + + val mockedClientException = new RuntimeException("client failed"); + + Mockito.doThrow(mockedClientException).when(wahlvorschlaegeClient).getWahlvorschlaege(wahlUndBezirkID); + + Assertions.assertThatException().isThrownBy(() -> unitUnderTest.getWahlvorschlaege(wahlUndBezirkID)).isSameAs(mockedClientException); + } + + @Test + void persistingExceptionDoesntPreventFromResultObject() { + val wahlID = "wahlID"; + val wahlbezirkID = "wahlbezirkID"; + val wahlUndBezirkID = new BezirkUndWahlID(wahlID, wahlbezirkID); + + val mockedWahlvorschlaegeModel = WahlvorschlaegeModel.builder().build(); + val mockedWahlvorschlaegeEntity = new Wahlvorschlaege(); + val mockedPersistingException = new RuntimeException("persisting failed"); + + Mockito.when(wahlvorschlaegeClient.getWahlvorschlaege(wahlUndBezirkID)).thenReturn(mockedWahlvorschlaegeModel); + Mockito.when(wahlvorschlaegeModelMapper.toEntity(mockedWahlvorschlaegeModel)).thenReturn(mockedWahlvorschlaegeEntity); + Mockito.doThrow(mockedPersistingException).when(wahlvorschlaegeRepository).save(mockedWahlvorschlaegeEntity); + + val result = unitUnderTest.getWahlvorschlaege(wahlUndBezirkID); + + Assertions.assertThat(result).isSameAs(mockedWahlvorschlaegeModel); + } + + } + +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeValidatorTest.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeValidatorTest.java new file mode 100644 index 000000000..6920dacfa --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/services/wahlvorschlag/WahlvorschlaegeValidatorTest.java @@ -0,0 +1,70 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.wahlvorschlag; + +import de.muenchen.oss.wahllokalsystem.basisdatenservice.exception.ExceptionConstants; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.FachlicheWlsException; +import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ExceptionFactory; +import de.muenchen.oss.wahllokalsystem.wls.common.security.domain.BezirkUndWahlID; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class WahlvorschlaegeValidatorTest { + + @Mock + ExceptionFactory exceptionFactory; + + @InjectMocks + WahlvorschlaegeValidator unitUnderTest; + + @Nested + class ValidWahlIdUndWahlbezirkIDOrThrow { + + private final FachlicheWlsException mockedFachlicheWlsException = FachlicheWlsException.withCode("").buildWithMessage(""); + + @Test + void noExceptionWhenValid() { + Assertions.assertThatNoException().isThrownBy(() -> unitUnderTest.validWahlIdUndWahlbezirkIDOrThrow(new BezirkUndWahlID("wahlID", "wahlbezirkID"))); + } + + @Test + void exceptionWhenParameterIsNull() { + Mockito.when(exceptionFactory.createFachlicheWlsException(ExceptionConstants.SUCHKRITERIEN_UNVOLLSTAENDIG)).thenReturn(mockedFachlicheWlsException); + Assertions.assertThatException().isThrownBy(() -> unitUnderTest.validWahlIdUndWahlbezirkIDOrThrow(null)).isSameAs(mockedFachlicheWlsException); + } + + @Test + void exceptionWhenWahlIDIsNull() { + Mockito.when(exceptionFactory.createFachlicheWlsException(ExceptionConstants.SUCHKRITERIEN_UNVOLLSTAENDIG)).thenReturn(mockedFachlicheWlsException); + Assertions.assertThatException().isThrownBy(() -> unitUnderTest.validWahlIdUndWahlbezirkIDOrThrow(new BezirkUndWahlID(null, "wahlbezirkID"))) + .isSameAs(mockedFachlicheWlsException); + } + + @Test + void exceptionWhenWahlIDIsEmptyString() { + Mockito.when(exceptionFactory.createFachlicheWlsException(ExceptionConstants.SUCHKRITERIEN_UNVOLLSTAENDIG)).thenReturn(mockedFachlicheWlsException); + Assertions.assertThatException().isThrownBy(() -> unitUnderTest.validWahlIdUndWahlbezirkIDOrThrow(new BezirkUndWahlID("", "wahlbezirkID"))) + .isSameAs(mockedFachlicheWlsException); + } + + @Test + void exceptionWhenWahlbezirkIDIsNull() { + Mockito.when(exceptionFactory.createFachlicheWlsException(ExceptionConstants.SUCHKRITERIEN_UNVOLLSTAENDIG)).thenReturn(mockedFachlicheWlsException); + Assertions.assertThatException().isThrownBy(() -> unitUnderTest.validWahlIdUndWahlbezirkIDOrThrow(new BezirkUndWahlID("wahlID", null))) + .isSameAs(mockedFachlicheWlsException); + } + + @Test + void exceptionWhenWahlbezirkIDIsEmptyString() { + Mockito.when(exceptionFactory.createFachlicheWlsException(ExceptionConstants.SUCHKRITERIEN_UNVOLLSTAENDIG)).thenReturn(mockedFachlicheWlsException); + Assertions.assertThatException().isThrownBy(() -> unitUnderTest.validWahlIdUndWahlbezirkIDOrThrow(new BezirkUndWahlID("wahlID", ""))) + .isSameAs(mockedFachlicheWlsException); + } + } + +} diff --git a/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/utils/Authorities.java b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/utils/Authorities.java new file mode 100644 index 000000000..e5f3b74d3 --- /dev/null +++ b/wls-basisdaten-service/src/test/java/de/muenchen/oss/wahllokalsystem/basisdatenservice/utils/Authorities.java @@ -0,0 +1,40 @@ +package de.muenchen.oss.wahllokalsystem.basisdatenservice.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class Authorities { + + public static final String SERVICE_GET_WAHLVORSCHLAEGE = "Basisdaten_BUSINESSACTION_GetWahlvorschlaege"; + + public static final String REPOSITORY_READ_WAHLVORSCHLAEGE = "Basisdaten_READ_WLSWahlvorschlaege"; + public static final String REPOSITORY_DELETE_WAHLVORSCHLAEGE = "Basisdaten_DELETE_WLSWahlvorschlaege"; + public static final String REPOSITORY_WRITE_WAHLVORSCHLAEGE = "Basisdaten_WRITE_WLSWahlvorschlaege"; + + public static final String REPOSITORY_READ_WAHLVORSCHLAG = "Basisdaten_READ_Wahlvorschlag"; + public static final String REPOSITORY_WRITE_WAHLVORSCHLAG = "Basisdaten_WRITE_Wahlvorschlag"; + public static final String REPOSITORY_DELETE_WAHLVORSCHLAG = "Basisdaten_DELETE_Wahlvorschlag"; + + public static final String REPOSITORY_READ_KANDIDAT = "Basisdaten_READ_Kandidat"; + public static final String REPOSITORY_WRITE_KANDIDAT = "Basisdaten_WRITE_Kandidat"; + public static final String REPOSITORY_DELETE_KANDIDAT = "Basisdaten_DELETE_Kandidat"; + + public static final String[] ALL_AUTHORITIES_GET_WAHLVORSCHLAEGE = new String[] { + SERVICE_GET_WAHLVORSCHLAEGE, + REPOSITORY_READ_WAHLVORSCHLAEGE, + REPOSITORY_WRITE_WAHLVORSCHLAEGE, + REPOSITORY_WRITE_WAHLVORSCHLAG, + REPOSITORY_WRITE_KANDIDAT + }; + public static final String[] ALL_AUTHORITIES_SET_WAHLVORSCHLAEGE = new String[] { + SERVICE_GET_WAHLVORSCHLAEGE, + REPOSITORY_READ_WAHLVORSCHLAEGE, + REPOSITORY_WRITE_WAHLVORSCHLAEGE + }; + + public static final String[] ALL_AUTHORITIES_DELETE_WAHLVORSCHLAEGE = new String[] { + REPOSITORY_DELETE_WAHLVORSCHLAEGE + }; + +} diff --git a/wls-basisdaten-service/src/main/resources/application-test.yml b/wls-basisdaten-service/src/test/resources/application-test.yml similarity index 78% rename from wls-basisdaten-service/src/main/resources/application-test.yml rename to wls-basisdaten-service/src/test/resources/application-test.yml index 562b25f44..7b6942275 100644 --- a/wls-basisdaten-service/src/main/resources/application-test.yml +++ b/wls-basisdaten-service/src/test/resources/application-test.yml @@ -1,5 +1,9 @@ -spring: +app: + clients: + eai: + basePath: http://localhost:${wiremock.server.port}/ +spring: # Spring JPA h2.console.enabled: true jpa: @@ -9,10 +13,13 @@ spring: # configuration for local (development) mode. this # is also the default, that spring offers by convention. # but here explicite: - ddl-auto: create-drop + ddl-auto: validate naming.physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl # Logging for database operation show-sql: true properties: hibernate: format_sql: true +wiremock: + server: + port: 0 diff --git a/wls-basisdaten-service/src/test/resources/http-client.env.json b/wls-basisdaten-service/src/test/resources/http-client.env.json new file mode 100644 index 000000000..5efb2e017 --- /dev/null +++ b/wls-basisdaten-service/src/test/resources/http-client.env.json @@ -0,0 +1,6 @@ +{ + "nonDocker": { + "WLS_BASISDATEN_SERVICE_URL": "http://localhost:39151", + "SSO_URL": "http://kubernetes.docker.internal:8100" + } +} \ No newline at end of file diff --git a/wls-basisdaten-service/src/test/resources/wahlvorstand.http b/wls-basisdaten-service/src/test/resources/wahlvorstand.http new file mode 100644 index 000000000..ceec5e39b --- /dev/null +++ b/wls-basisdaten-service/src/test/resources/wahlvorstand.http @@ -0,0 +1,22 @@ +### Get token wls_all +POST {{ SSO_URL }}/auth/realms/wls_realm/protocol/openid-connect/token +Content-Type: application/x-www-form-urlencoded + +password = test & +grant_type = password & +client_secret = top-secret & +client_id = wls & +username = wls_all + +> {% + client.global.set("auth_token", response.body.access_token); + client.global.set("token_type", response.body.token_type); +%} + +### get userinfo with auth_token +GET {{ SSO_URL }}/auth/realms/wls_realm/protocol/openid-connect/userinfo +Authorization: {{ token_type }} {{ auth_token }} + +### Get Basisdaten - working with dummy.client +GET {{ WLS_BASISDATEN_SERVICE_URL }}/businessActions/wahlvorschlaege/wahlID/wahlbezirkID +Authorization: {{ token_type }} {{ auth_token }} \ No newline at end of file