refactoring for implicit creation of dependend hosting-assets (#108)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/108 Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
		
							
								
								
									
										27
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								README.md
									
									
									
									
									
								
							| @@ -550,12 +550,37 @@ Dependency versions can be automatically upgraded to the latest available versio | |||||||
| gw useLatestVersions | gw useLatestVersions | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Afterwards, `gw check` is automatically started. | Afterward, `gw check` is automatically started. | ||||||
| Please only commit+push to master if the check run shows no errors. | Please only commit+push to master if the check run shows no errors. | ||||||
|  |  | ||||||
| More infos, e.g. on blacklists see on the [project's website](https://github.com/patrikerdes/gradle-use-latest-versions-plugin). | More infos, e.g. on blacklists see on the [project's website](https://github.com/patrikerdes/gradle-use-latest-versions-plugin). | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Biggest Flaws in our Architecture | ||||||
|  |  | ||||||
|  | ### The RBAC System is too Complicated | ||||||
|  |  | ||||||
|  | Now, where we have a better experience with what we really need from the RBAC system, we have learned | ||||||
|  | that and creates too many (grant- and role-) rows and too even tables which could be avoided completely. | ||||||
|  |  | ||||||
|  | The basic idea is always to always have a fixed set of ordered role-types which apply for all DB-tables under RBAC, | ||||||
|  | e.g. OWNER>ADMIN>AGENT\[>PROXY?\]>TENENT>REFERRER. | ||||||
|  | Grants between these for the same DB-row would be implicit by order comparision. | ||||||
|  | This way we would get rid of all explicit grants within the same DB-row | ||||||
|  | and would not need the `rbac.role` table anymore. | ||||||
|  | We would also reduce the depth of the expensive recursive CTE-query. | ||||||
|  |  | ||||||
|  | This has to be explored further. | ||||||
|  | For now, we just keep it in mind and   | ||||||
|  |  | ||||||
|  | ### The Mapper is Error-Prone | ||||||
|  |  | ||||||
|  | Where `org.modelmapper.ModelMapper` reduces bloat-code a lot and has some nice features about recursive data-structure mappings, | ||||||
|  | it often causes strange errors which are hard to fix. | ||||||
|  | E.g. the uuid of the target main object is often taken from an uuid of a sub-subject. | ||||||
|  | (For now, use `StrictMapper` to avoid this, for the case it happens.) | ||||||
|  |  | ||||||
|  |  | ||||||
| ## How To ... | ## How To ... | ||||||
|  |  | ||||||
| ### How to Configure .pgpass for the Default PostgreSQL Database? | ### How to Configure .pgpass for the Default PostgreSQL Database? | ||||||
|   | |||||||
| @@ -9,15 +9,25 @@ import java.lang.annotation.Target; | |||||||
| @Target(ElementType.TYPE) | @Target(ElementType.TYPE) | ||||||
| @Retention(RetentionPolicy.RUNTIME) | @Retention(RetentionPolicy.RUNTIME) | ||||||
| public @interface DisplayAs { | public @interface DisplayAs { | ||||||
|  |  | ||||||
|     class DisplayName { |     class DisplayName { | ||||||
|  |  | ||||||
|         public static String of(final Class<?> clazz) { |         public static String of(final Class<?> clazz) { | ||||||
|             final var displayNameAnnot = clazz.getAnnotation(DisplayAs.class); |             final var displayNameAnnot = getDisplayNameAnnotation(clazz); | ||||||
|             return displayNameAnnot != null ? displayNameAnnot.value() : clazz.getSimpleName(); |             return displayNameAnnot != null ? displayNameAnnot.value() : clazz.getSimpleName(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public static String of(@NotNull final Object instance) { |         public static String of(@NotNull final Object instance) { | ||||||
|             return of(instance.getClass()); |             return of(instance.getClass()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private static DisplayAs getDisplayNameAnnotation(final Class<?> clazz) { | ||||||
|  |             if (clazz == null) { | ||||||
|  |                 return null; | ||||||
|  |             } | ||||||
|  |             final var annot = clazz.getAnnotation(DisplayAs.class); | ||||||
|  |             return annot != null ? annot : getDisplayNameAnnotation(clazz.getSuperclass()); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     String value() default ""; |     String value() default ""; | ||||||
|   | |||||||
| @@ -5,17 +5,18 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingItemsA | |||||||
| import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource; | import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource; | ||||||
| import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource; | import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource; | ||||||
| import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource; | import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource; | ||||||
|  | import net.hostsharing.hsadminng.hs.booking.item.validators.BookingItemEntitySaveProcessor; | ||||||
| import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry; | import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry; | ||||||
|  | import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity; | ||||||
| import net.hostsharing.hsadminng.mapper.KeyValueMap; | import net.hostsharing.hsadminng.mapper.KeyValueMap; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StrictMapper; | ||||||
|  | import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.http.ResponseEntity; | import org.springframework.http.ResponseEntity; | ||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| import org.springframework.web.bind.annotation.RestController; | import org.springframework.web.bind.annotation.RestController; | ||||||
| import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; | import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; | ||||||
|  |  | ||||||
| import jakarta.persistence.EntityManager; |  | ||||||
| import jakarta.persistence.PersistenceContext; |  | ||||||
| import java.time.LocalDate; | import java.time.LocalDate; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| @@ -30,13 +31,13 @@ public class HsBookingItemController implements HsBookingItemsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StrictMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsBookingItemRbacRepository bookingItemRepo; |     private HsBookingItemRbacRepository bookingItemRepo; | ||||||
|  |  | ||||||
|     @PersistenceContext |     @Autowired | ||||||
|     private EntityManager em; |     private EntityManagerWrapper em; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     @Transactional(readOnly = true) |     @Transactional(readOnly = true) | ||||||
| @@ -48,7 +49,7 @@ public class HsBookingItemController implements HsBookingItemsApi { | |||||||
|  |  | ||||||
|         final var entities = bookingItemRepo.findAllByProjectUuid(projectUuid); |         final var entities = bookingItemRepo.findAllByProjectUuid(projectUuid); | ||||||
|  |  | ||||||
|         final var resources = mapper.mapList(entities, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); |         final var resources = mapper.mapList(entities, HsBookingItemResource.class, RBAC_ENTITY_TO_RESOURCE_POSTMAPPER); | ||||||
|         return ResponseEntity.ok(resources); |         return ResponseEntity.ok(resources); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -62,15 +63,20 @@ public class HsBookingItemController implements HsBookingItemsApi { | |||||||
|         context.define(currentSubject, assumedRoles); |         context.define(currentSubject, assumedRoles); | ||||||
|  |  | ||||||
|         final var entityToSave = mapper.map(body, HsBookingItemRbacEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER); |         final var entityToSave = mapper.map(body, HsBookingItemRbacEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER); | ||||||
|  |         final var mapped = new BookingItemEntitySaveProcessor(em, entityToSave) | ||||||
|         final var saved = HsBookingItemEntityValidatorRegistry.validated(em, bookingItemRepo.save(entityToSave)); |                 .preprocessEntity() | ||||||
|  |                 .validateEntity() | ||||||
|  |                 .prepareForSave() | ||||||
|  |                 .save() | ||||||
|  |                 .validateContext() | ||||||
|  |                 .mapUsing(e -> mapper.map(e, HsBookingItemResource.class, ITEM_TO_RESOURCE_POSTMAPPER)) | ||||||
|  |                 .revampProperties(); | ||||||
|  |  | ||||||
|         final var uri = |         final var uri = | ||||||
|                 MvcUriComponentsBuilder.fromController(getClass()) |                 MvcUriComponentsBuilder.fromController(getClass()) | ||||||
|                         .path("/api/hs/booking/items/{id}") |                         .path("/api/hs/booking/items/{id}") | ||||||
|                         .buildAndExpand(saved.getUuid()) |                         .buildAndExpand(mapped.getUuid()) | ||||||
|                         .toUri(); |                         .toUri(); | ||||||
|         final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); |  | ||||||
|         return ResponseEntity.created(uri).body(mapped); |         return ResponseEntity.created(uri).body(mapped); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -87,7 +93,7 @@ public class HsBookingItemController implements HsBookingItemsApi { | |||||||
|         result.ifPresent(entity -> em.detach(entity)); // prevent further LAZY-loading |         result.ifPresent(entity -> em.detach(entity)); // prevent further LAZY-loading | ||||||
|         return result |         return result | ||||||
|                 .map(bookingItemEntity -> ResponseEntity.ok( |                 .map(bookingItemEntity -> ResponseEntity.ok( | ||||||
|                         mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER))) |                         mapper.map(bookingItemEntity, HsBookingItemResource.class, RBAC_ENTITY_TO_RESOURCE_POSTMAPPER))) | ||||||
|                 .orElseGet(() -> ResponseEntity.notFound().build()); |                 .orElseGet(() -> ResponseEntity.notFound().build()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -120,18 +126,21 @@ public class HsBookingItemController implements HsBookingItemsApi { | |||||||
|         new HsBookingItemEntityPatcher(current).apply(body); |         new HsBookingItemEntityPatcher(current).apply(body); | ||||||
|  |  | ||||||
|         final var saved = bookingItemRepo.save(HsBookingItemEntityValidatorRegistry.validated(em, current)); |         final var saved = bookingItemRepo.save(HsBookingItemEntityValidatorRegistry.validated(em, current)); | ||||||
|         final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); |         final var mapped = mapper.map(saved, HsBookingItemResource.class, RBAC_ENTITY_TO_RESOURCE_POSTMAPPER); | ||||||
|         return ResponseEntity.ok(mapped); |         return ResponseEntity.ok(mapped); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     final BiConsumer<HsBookingItemRbacEntity, HsBookingItemResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { |     final BiConsumer<HsBookingItem, HsBookingItemResource> ITEM_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { | ||||||
|         resource.setValidFrom(entity.getValidity().lower()); |         resource.setValidFrom(entity.getValidity().lower()); | ||||||
|         if (entity.getValidity().hasUpperBound()) { |         if (entity.getValidity().hasUpperBound()) { | ||||||
|             resource.setValidTo(entity.getValidity().upper().minusDays(1)); |             resource.setValidTo(entity.getValidity().upper().minusDays(1)); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|  |     final BiConsumer<HsBookingItemRbacEntity, HsBookingItemResource> RBAC_ENTITY_TO_RESOURCE_POSTMAPPER = ITEM_TO_RESOURCE_POSTMAPPER::accept; | ||||||
|  |  | ||||||
|     final BiConsumer<HsBookingItemInsertResource, HsBookingItemRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { |     final BiConsumer<HsBookingItemInsertResource, HsBookingItemRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { | ||||||
|  |         entity.setProject(em.find(HsBookingProjectRealEntity.class, resource.getProjectUuid())); | ||||||
|         entity.setValidity(toPostgresDateRange(LocalDate.now(), resource.getValidTo())); |         entity.setValidity(toPostgresDateRange(LocalDate.now(), resource.getValidTo())); | ||||||
|         entity.putResources(KeyValueMap.from(resource.getResources())); |         entity.putResources(KeyValueMap.from(resource.getResources())); | ||||||
|     }; |     }; | ||||||
|   | |||||||
| @@ -0,0 +1,131 @@ | |||||||
|  | package net.hostsharing.hsadminng.hs.booking.item.validators; | ||||||
|  |  | ||||||
|  | import net.hostsharing.hsadminng.errors.MultiValidationException; | ||||||
|  | import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource; | ||||||
|  | import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem; | ||||||
|  | import net.hostsharing.hsadminng.hs.validation.HsEntityValidator; | ||||||
|  |  | ||||||
|  | import jakarta.persistence.EntityManager; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.function.Function; | ||||||
|  | import java.util.regex.Pattern; | ||||||
|  |  | ||||||
|  | // TODO.refa: introduce common base class with HsHostingAssetEntitySaveProcessor | ||||||
|  | /** | ||||||
|  |  * Wraps the steps of the pararation, validation, mapping and revamp around saving of a HsBookingItem into a readable API. | ||||||
|  |  */ | ||||||
|  | public class BookingItemEntitySaveProcessor { | ||||||
|  |  | ||||||
|  |     private final HsEntityValidator<HsBookingItem> validator; | ||||||
|  |     private String expectedStep = "preprocessEntity"; | ||||||
|  |     private final EntityManager em; | ||||||
|  |     private HsBookingItem entity; | ||||||
|  |     private HsBookingItemResource resource; | ||||||
|  |  | ||||||
|  |     public BookingItemEntitySaveProcessor(final EntityManager em, final HsBookingItem entity) { | ||||||
|  |         this.em = em; | ||||||
|  |         this.entity = entity; | ||||||
|  |         this.validator = HsBookingItemEntityValidatorRegistry.forType(entity.getType()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// initial step allowing to set default values before any validations | ||||||
|  |     public BookingItemEntitySaveProcessor preprocessEntity() { | ||||||
|  |         step("preprocessEntity", "validateEntity"); | ||||||
|  |         validator.preprocessEntity(entity); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// validates the entity itself including its properties | ||||||
|  |     public BookingItemEntitySaveProcessor validateEntity() { | ||||||
|  |         step("validateEntity", "prepareForSave"); | ||||||
|  |         MultiValidationException.throwIfNotEmpty(validator.validateEntity(entity)); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO.impl: remove once the migration of legacy data is done | ||||||
|  |     /// validates the entity itself including its properties, but ignoring some error messages for import of legacy data | ||||||
|  |     public BookingItemEntitySaveProcessor validateEntityIgnoring(final String... ignoreRegExp) { | ||||||
|  |         step("validateEntity", "prepareForSave"); | ||||||
|  |         final var ignoreRegExpPatterns = Arrays.stream(ignoreRegExp).map(Pattern::compile).toList(); | ||||||
|  |         MultiValidationException.throwIfNotEmpty( | ||||||
|  |                 validator.validateEntity(entity).stream() | ||||||
|  |                         .filter(error -> ignoreRegExpPatterns.stream().noneMatch(p -> p.matcher(error).matches() )) | ||||||
|  |                         .toList() | ||||||
|  |         ); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// hashing passwords etc. | ||||||
|  |     public BookingItemEntitySaveProcessor prepareForSave() { | ||||||
|  |         step("prepareForSave", "save"); | ||||||
|  |         validator.prepareProperties(em, entity); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Saves the entity using the given `saveFunction`. | ||||||
|  |      * | ||||||
|  |      * <p>`validator.postPersist(em, entity)` is NOT called. | ||||||
|  |      * If any postprocessing is necessary, the saveFunction has to implement this.</p> | ||||||
|  |      * @param saveFunction | ||||||
|  |      * @return this | ||||||
|  |      */ | ||||||
|  |     public BookingItemEntitySaveProcessor saveUsing(final Function<HsBookingItem, HsBookingItem> saveFunction) { | ||||||
|  |         step("save", "validateContext"); | ||||||
|  |         entity = saveFunction.apply(entity); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Saves the using the `EntityManager`, but does NOT ever merge the entity. | ||||||
|  |      * | ||||||
|  |      * <p>`validator.postPersist(em, entity)` is called afterwards with the entity guaranteed to be flushed to the database.</p> | ||||||
|  |      * @return this | ||||||
|  |      */ | ||||||
|  |     public BookingItemEntitySaveProcessor save() { | ||||||
|  |         return saveUsing(e -> { | ||||||
|  |             if (!em.contains(entity)) { | ||||||
|  |                 em.persist(entity); | ||||||
|  |             } | ||||||
|  |             em.flush(); // makes RbacEntity available as RealEntity if needed | ||||||
|  |             validator.postPersist(em, entity); | ||||||
|  |             return entity; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// validates the entity within it's parent and child hierarchy (e.g. totals validators and other limits) | ||||||
|  |     public BookingItemEntitySaveProcessor validateContext() { | ||||||
|  |         step("validateContext", "mapUsing"); | ||||||
|  |         return HsEntityValidator.doWithEntityManager(em, () -> { | ||||||
|  |             MultiValidationException.throwIfNotEmpty(validator.validateContext(entity)); | ||||||
|  |             return this; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// maps entity to JSON resource representation | ||||||
|  |     public BookingItemEntitySaveProcessor mapUsing( | ||||||
|  |             final Function<HsBookingItem, HsBookingItemResource> mapFunction) { | ||||||
|  |         step("mapUsing", "revampProperties"); | ||||||
|  |         resource = mapFunction.apply(entity); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// removes write-only-properties and ads computed-properties | ||||||
|  |     @SuppressWarnings("unchecked") | ||||||
|  |     public HsBookingItemResource revampProperties() { | ||||||
|  |         step("revampProperties", null); | ||||||
|  |         final var revampedProps = validator.revampProperties(em, entity, (Map<String, Object>) resource.getResources()); | ||||||
|  |         resource.setResources(revampedProps); | ||||||
|  |         return resource; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Makes sure that the steps are called in the correct order. | ||||||
|  |     // Could also be implemented using an interface per method, but that seems exaggerated. | ||||||
|  |     private void step(final String current, final String next) { | ||||||
|  |         if (!expectedStep.equals(current)) { | ||||||
|  |             throw new IllegalStateException("expected " + expectedStep + " but got " + current); | ||||||
|  |         } | ||||||
|  |         expectedStep = next; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -48,10 +48,11 @@ public class HsBookingItemEntityValidatorRegistry { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static List<String> doValidate(final EntityManager em, final HsBookingItem bookingItem) { |     public static List<String> doValidate(final EntityManager em, final HsBookingItem bookingItem) { | ||||||
|  |         final var bookingItemValidator = HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()); | ||||||
|         return HsEntityValidator.doWithEntityManager(em, () -> |         return HsEntityValidator.doWithEntityManager(em, () -> | ||||||
|             HsEntityValidator.sequentiallyValidate( |             HsEntityValidator.sequentiallyValidate( | ||||||
|                     () -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateEntity(bookingItem), |                     () -> bookingItemValidator.validateEntity(bookingItem), | ||||||
|                     () -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem)) |                     () -> bookingItemValidator.validateContext(bookingItem)) | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  |  | ||||||
| package net.hostsharing.hsadminng.hs.booking.item.validators; | package net.hostsharing.hsadminng.hs.booking.item.validators; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem; | import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem; | ||||||
| @@ -15,6 +16,9 @@ class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator { | |||||||
|  |  | ||||||
|     public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}"; |     public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}"; | ||||||
|     public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName"; |     public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName"; | ||||||
|  |     public static final String TARGET_UNIX_USER_PROPERTY_NAME = "targetUnixUser"; | ||||||
|  |     public static final String WEBSPACE_NAME_REGEX = "[a-z][a-z0-9]{2}[0-9]{2}"; | ||||||
|  |     public static final String TARGET_UNIX_USER_NAME_REGEX = "^"+WEBSPACE_NAME_REGEX+"$|^"+WEBSPACE_NAME_REGEX+"-[a-z0-9\\._-]+$"; | ||||||
|     public static final String VERIFICATION_CODE_PROPERTY_NAME = "verificationCode"; |     public static final String VERIFICATION_CODE_PROPERTY_NAME = "verificationCode"; | ||||||
|  |  | ||||||
|     HsDomainSetupBookingItemValidator() { |     HsDomainSetupBookingItemValidator() { | ||||||
| @@ -24,6 +28,12 @@ class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator { | |||||||
|                         .matchesRegEx(FQDN_REGEX).describedAs("is not a (non-top-level) fully qualified domain name") |                         .matchesRegEx(FQDN_REGEX).describedAs("is not a (non-top-level) fully qualified domain name") | ||||||
|                         .notMatchesRegEx(REGISTRAR_LEVEL_DOMAINS).describedAs("is a forbidden registrar-level domain name") |                         .notMatchesRegEx(REGISTRAR_LEVEL_DOMAINS).describedAs("is a forbidden registrar-level domain name") | ||||||
|                         .required(), |                         .required(), | ||||||
|  |                 // TODO.legacy: remove the following property once we give up legacy compatibility | ||||||
|  |                 stringProperty(TARGET_UNIX_USER_PROPERTY_NAME).writeOnce() | ||||||
|  |                         .maxLength(253) | ||||||
|  |                         .matchesRegEx(TARGET_UNIX_USER_NAME_REGEX).describedAs("is not a valid unix-user name") | ||||||
|  |                         .writeOnce() | ||||||
|  |                         .required(), | ||||||
|                 stringProperty(VERIFICATION_CODE_PROPERTY_NAME) |                 stringProperty(VERIFICATION_CODE_PROPERTY_NAME) | ||||||
|                         .minLength(12) |                         .minLength(12) | ||||||
|                         .maxLength(64) |                         .maxLength(64) | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingProjec | |||||||
| import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectInsertResource; | import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectInsertResource; | ||||||
| import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource; | import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource; | ||||||
| import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectResource; | import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectResource; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.http.ResponseEntity; | import org.springframework.http.ResponseEntity; | ||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| @@ -25,7 +25,7 @@ public class HsBookingProjectController implements HsBookingProjectsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsBookingProjectRbacRepository bookingProjectRepo; |     private HsBookingProjectRbacRepository bookingProjectRepo; | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAsse | |||||||
| import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource; | import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource; | ||||||
| import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource; | import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource; | ||||||
| import net.hostsharing.hsadminng.mapper.KeyValueMap; | import net.hostsharing.hsadminng.mapper.KeyValueMap; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; | import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.http.ResponseEntity; | import org.springframework.http.ResponseEntity; | ||||||
| @@ -35,7 +35,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsHostingAssetRbacRepository rbacAssetRepo; |     private HsHostingAssetRbacRepository rbacAssetRepo; | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context; | |||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeBankAccountsApi; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeBankAccountsApi; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountInsertResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountInsertResource; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountResource; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.iban4j.BicUtil; | import org.iban4j.BicUtil; | ||||||
| import org.iban4j.IbanUtil; | import org.iban4j.IbanUtil; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| @@ -24,7 +24,7 @@ public class HsOfficeBankAccountController implements HsOfficeBankAccountsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeBankAccountRepository bankAccountRepo; |     private HsOfficeBankAccountRepository bankAccountRepo; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package net.hostsharing.hsadminng.hs.office.contact; | package net.hostsharing.hsadminng.hs.office.contact; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeContactsApi; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeContactsApi; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource; | ||||||
| @@ -26,7 +26,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeContactRbacRepository contactRepo; |     private HsOfficeContactRbacRepository contactRepo; | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context; | |||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*; | ||||||
| import net.hostsharing.hsadminng.errors.MultiValidationException; | import net.hostsharing.hsadminng.errors.MultiValidationException; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.format.annotation.DateTimeFormat; | import org.springframework.format.annotation.DateTimeFormat; | ||||||
| import org.springframework.format.annotation.DateTimeFormat.ISO; | import org.springframework.format.annotation.DateTimeFormat.ISO; | ||||||
| @@ -29,7 +29,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo; |     private HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo; | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopShar | |||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource; | ||||||
| import net.hostsharing.hsadminng.errors.MultiValidationException; | import net.hostsharing.hsadminng.errors.MultiValidationException; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.format.annotation.DateTimeFormat; | import org.springframework.format.annotation.DateTimeFormat; | ||||||
| import org.springframework.format.annotation.DateTimeFormat.ISO; | import org.springframework.format.annotation.DateTimeFormat.ISO; | ||||||
| @@ -31,7 +31,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; |     private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; | ||||||
|   | |||||||
| @@ -7,8 +7,8 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebito | |||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource; | ||||||
| import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; | import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; | ||||||
| import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; | import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.rbac.object.BaseEntity; | import net.hostsharing.hsadminng.persistence.EntityExistsValidator; | ||||||
| import org.apache.commons.lang3.Validate; | import org.apache.commons.lang3.Validate; | ||||||
| import org.hibernate.Hibernate; | import org.hibernate.Hibernate; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| @@ -23,7 +23,6 @@ import jakarta.validation.ValidationException; | |||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
|  |  | ||||||
| import static net.hostsharing.hsadminng.errors.DisplayAs.DisplayName; |  | ||||||
| import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR; | import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR; | ||||||
|  |  | ||||||
| @RestController | @RestController | ||||||
| @@ -34,7 +33,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeDebitorRepository debitorRepo; |     private HsOfficeDebitorRepository debitorRepo; | ||||||
| @@ -42,6 +41,9 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { | |||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeRelationRealRepository relrealRepo; |     private HsOfficeRelationRealRepository relrealRepo; | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     private EntityExistsValidator entityValidator; | ||||||
|  |  | ||||||
|     @PersistenceContext |     @PersistenceContext | ||||||
|     private EntityManager em; |     private EntityManager em; | ||||||
|  |  | ||||||
| @@ -84,10 +86,10 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { | |||||||
|         final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class); |         final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class); | ||||||
|         if ( body.getDebitorRel() != null ) { |         if ( body.getDebitorRel() != null ) { | ||||||
|             body.getDebitorRel().setType(DEBITOR.name()); |             body.getDebitorRel().setType(DEBITOR.name()); | ||||||
|             final var debitorRel = mapper.map(body.getDebitorRel(), HsOfficeRelationRealEntity.class); |             final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class); | ||||||
|             validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor()); |             entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor()); | ||||||
|             validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder()); |             entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder()); | ||||||
|             validateEntityExists("debitorRel.contactUuid", debitorRel.getContact()); |             entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact()); | ||||||
|             entityToSave.setDebitorRel(relrealRepo.save(debitorRel)); |             entityToSave.setDebitorRel(relrealRepo.save(debitorRel)); | ||||||
|         } else { |         } else { | ||||||
|             final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid()); |             final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid()); | ||||||
| @@ -160,15 +162,4 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { | |||||||
|         final var mapped = mapper.map(saved, HsOfficeDebitorResource.class); |         final var mapped = mapper.map(saved, HsOfficeDebitorResource.class); | ||||||
|         return ResponseEntity.ok(mapped); |         return ResponseEntity.ok(mapped); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // TODO.impl: extract this to some generally usable class? |  | ||||||
|     private <T extends BaseEntity<T>> T validateEntityExists(final String property, final T entitySkeleton) { |  | ||||||
|         final var foundEntity = em.find(entitySkeleton.getClass(), entitySkeleton.getUuid()); |  | ||||||
|         if ( foundEntity == null) { |  | ||||||
|             throw new ValidationException("Unable to find " + DisplayName.of(entitySkeleton) + " by " + property + ": " + entitySkeleton.getUuid()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         //noinspection unchecked |  | ||||||
|         return (T) foundEntity; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeMembersh | |||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.http.ResponseEntity; | import org.springframework.http.ResponseEntity; | ||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| @@ -24,7 +24,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeMembershipRepository membershipRepo; |     private HsOfficeMembershipRepository membershipRepo; | ||||||
|   | |||||||
| @@ -2,18 +2,18 @@ package net.hostsharing.hsadminng.hs.office.membership; | |||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; | ||||||
| import net.hostsharing.hsadminng.mapper.EntityPatcher; | import net.hostsharing.hsadminng.mapper.EntityPatcher; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.mapper.OptionalFromJson; | import net.hostsharing.hsadminng.mapper.OptionalFromJson; | ||||||
|  |  | ||||||
| import java.util.Optional; | import java.util.Optional; | ||||||
|  |  | ||||||
| public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> { | public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> { | ||||||
|  |  | ||||||
|     private final Mapper mapper; |     private final StandardMapper mapper; | ||||||
|     private final HsOfficeMembershipEntity entity; |     private final HsOfficeMembershipEntity entity; | ||||||
|  |  | ||||||
|     public HsOfficeMembershipEntityPatcher( |     public HsOfficeMembershipEntityPatcher( | ||||||
|             final Mapper mapper, |             final StandardMapper mapper, | ||||||
|             final HsOfficeMembershipEntity entity) { |             final HsOfficeMembershipEntity entity) { | ||||||
|         this.mapper = mapper; |         this.mapper = mapper; | ||||||
|         this.entity = entity; |         this.entity = entity; | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; | |||||||
| import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; | import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; | ||||||
| import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; | import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; | ||||||
| import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType; | import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.rbac.object.BaseEntity; | import net.hostsharing.hsadminng.rbac.object.BaseEntity; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.http.HttpStatus; | import org.springframework.http.HttpStatus; | ||||||
| @@ -36,7 +36,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficePartnerRepository partnerRepo; |     private HsOfficePartnerRepository partnerRepo; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package net.hostsharing.hsadminng.hs.office.person; | package net.hostsharing.hsadminng.hs.office.person; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePersonsApi; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePersonsApi; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource; | ||||||
| @@ -23,7 +23,7 @@ public class HsOfficePersonController implements HsOfficePersonsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficePersonRepository personRepo; |     private HsOfficePersonRepository personRepo; | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository | |||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationsApi; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationsApi; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*; | ||||||
| import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; | import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.http.ResponseEntity; | import org.springframework.http.ResponseEntity; | ||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| @@ -28,7 +28,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeRelationRbacRepository relationRbacRepo; |     private HsOfficeRelationRbacRepository relationRbacRepo; | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeSepaMand | |||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateInsertResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateInsertResource; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.http.ResponseEntity; | import org.springframework.http.ResponseEntity; | ||||||
| import org.springframework.transaction.annotation.Transactional; | import org.springframework.transaction.annotation.Transactional; | ||||||
| @@ -28,7 +28,7 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private HsOfficeSepaMandateRepository sepaMandateRepo; |     private HsOfficeSepaMandateRepository sepaMandateRepo; | ||||||
|   | |||||||
| @@ -130,7 +130,7 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Map<String, Object> revampProperties(final EntityManager em, final E entity, final Map<String, Object> config) { |     public Map<String, Object> revampProperties(final EntityManager em, final E entity, final Map<String, Object> config) { | ||||||
|         final var copy = new HashMap<>(config); |         final var copy = config != null ? new HashMap<>(config) : new HashMap(); | ||||||
|         stream(propertyValidators).forEach(p -> { |         stream(propertyValidators).forEach(p -> { | ||||||
|             if (p.isWriteOnly()) { |             if (p.isWriteOnly()) { | ||||||
|                 copy.remove(p.propertyName); |                 copy.remove(p.propertyName); | ||||||
|   | |||||||
| @@ -11,18 +11,20 @@ import java.lang.reflect.Field; | |||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.function.BiConsumer; | import java.util.function.BiConsumer; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
|  | import java.util.stream.Stream; | ||||||
|  |  | ||||||
|  | import static java.util.Arrays.stream; | ||||||
| import static net.hostsharing.hsadminng.errors.DisplayAs.DisplayName; | import static net.hostsharing.hsadminng.errors.DisplayAs.DisplayName; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * A nicer API for ModelMapper. |  * A nicer API for ModelMapper. | ||||||
|  */ |  */ | ||||||
| public class Mapper extends ModelMapper { | abstract class Mapper extends ModelMapper { | ||||||
|  |  | ||||||
|     @PersistenceContext |     @PersistenceContext | ||||||
|     EntityManager em; |     EntityManager em; | ||||||
|  |  | ||||||
|     public Mapper() { |     Mapper() { | ||||||
|         getConfiguration().setAmbiguityIgnored(true); |         getConfiguration().setAmbiguityIgnored(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -45,8 +47,12 @@ public class Mapper extends ModelMapper { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public <D> D map(final Object source, final Class<D> destinationType) { |     public <D> D map(final Object source, final Class<D> destinationType) { | ||||||
|  |         return map("", source, destinationType); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public <D> D map(final String namePrefix, final Object source, final Class<D> destinationType) { | ||||||
|         final var target = super.map(source, destinationType); |         final var target = super.map(source, destinationType); | ||||||
|         for (Field f : destinationType.getDeclaredFields()) { |         for (Field f : getDeclaredFieldsIncludingSuperClasses(destinationType)) { | ||||||
|             if (f.getAnnotation(ManyToOne.class) == null) { |             if (f.getAnnotation(ManyToOne.class) == null) { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| @@ -64,18 +70,30 @@ public class Mapper extends ModelMapper { | |||||||
|             if (subEntityUuid == null) { |             if (subEntityUuid == null) { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             ReflectionUtils.setField(f, target, findEntityById(f.getType(), subEntityUuid)); |             ReflectionUtils.setField(f, target, fetchEntity(namePrefix + f.getName() + ".uuid", f.getType(), subEntityUuid)); | ||||||
|         } |         } | ||||||
|         return target; |         return target; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private Object findEntityById(final Class<?> entityClass, final Object subEntityUuid) { |     private static <D> Field[] getDeclaredFieldsIncludingSuperClasses(final Class<D> destinationType) { | ||||||
|         // using getReference would be more efficent, but results in very technical error messages |         if (destinationType == null) { | ||||||
|         final var entity = em.find(entityClass, subEntityUuid); |             return new Field[0]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return Stream.concat( | ||||||
|  |                 stream(destinationType.getDeclaredFields()), | ||||||
|  |                 stream(getDeclaredFieldsIncludingSuperClasses(destinationType.getSuperclass()))) | ||||||
|  |             .toArray(Field[]::new); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public <E> E fetchEntity(final String propertyName, final Class<E> entityClass, final Object subEntityUuid) { | ||||||
|  |         final var entity = em.getReference(entityClass, subEntityUuid); | ||||||
|         if (entity != null) { |         if (entity != null) { | ||||||
|             return entity; |             return entity; | ||||||
|         } |         } | ||||||
|         throw new ValidationException("Unable to find " + DisplayName.of(entityClass) + " by uuid: " + subEntityUuid); |         throw new ValidationException( | ||||||
|  |                 "Unable to find " + DisplayName.of(entityClass) + | ||||||
|  |                 " by " + propertyName + ": " + subEntityUuid); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public <S, T> T map(final S source, final Class<T> targetClass, final BiConsumer<S, T> postMapper) { |     public <S, T> T map(final S source, final Class<T> targetClass, final BiConsumer<S, T> postMapper) { | ||||||
| @@ -86,4 +104,13 @@ public class Mapper extends ModelMapper { | |||||||
|         postMapper.accept(source, target); |         postMapper.accept(source, target); | ||||||
|         return target; |         return target; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public <S, T> T map(final String namePrefix, final S source, final Class<T> targetClass, final BiConsumer<S, T> postMapper) { | ||||||
|  |         if (source == null) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         final var target = map(source, targetClass); | ||||||
|  |         postMapper.accept(source, target); | ||||||
|  |         return target; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,13 +0,0 @@ | |||||||
| package net.hostsharing.hsadminng.mapper; |  | ||||||
|  |  | ||||||
| import org.springframework.context.annotation.Bean; |  | ||||||
| import org.springframework.context.annotation.Configuration; |  | ||||||
|  |  | ||||||
| @Configuration |  | ||||||
| public class MapperConfiguration { |  | ||||||
|  |  | ||||||
|     @Bean |  | ||||||
|     public Mapper modelMapper() { |  | ||||||
|         return new Mapper(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | package net.hostsharing.hsadminng.mapper; | ||||||
|  |  | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A nicer API for ModelMapper in standard mode. | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | public class StandardMapper extends Mapper { | ||||||
|  |  | ||||||
|  |     public StandardMapper() { | ||||||
|  |         getConfiguration().setAmbiguityIgnored(true); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,19 @@ | |||||||
|  | package net.hostsharing.hsadminng.mapper; | ||||||
|  |  | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import static org.modelmapper.convention.MatchingStrategies.STRICT; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A nicer API for ModelMapper in strict mode. | ||||||
|  |  * | ||||||
|  |  * <p>This makes sure that resource.whateverUuid does not accidentally get mapped to entity.uuid, | ||||||
|  |  * if resource.uuid does not exist.</p> | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | public class StrictMapper extends Mapper { | ||||||
|  |  | ||||||
|  |     public StrictMapper() { | ||||||
|  |         getConfiguration().setMatchingStrategy(STRICT); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | package net.hostsharing.hsadminng.persistence; | ||||||
|  |  | ||||||
|  | import net.hostsharing.hsadminng.errors.DisplayAs.DisplayName; | ||||||
|  | import net.hostsharing.hsadminng.rbac.object.BaseEntity; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Service; | ||||||
|  |  | ||||||
|  | import jakarta.persistence.Entity; | ||||||
|  | import jakarta.validation.ValidationException; | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | public class EntityExistsValidator { | ||||||
|  |  | ||||||
|  |     @Autowired | ||||||
|  |     private EntityManagerWrapper em; | ||||||
|  |  | ||||||
|  |     public <T extends BaseEntity<T>> void validateEntityExists(final String property, final T entitySkeleton) { | ||||||
|  |         final var foundEntity = em.find(entityClass(entitySkeleton), entitySkeleton.getUuid()); | ||||||
|  |         if ( foundEntity == null) { | ||||||
|  |             throw new ValidationException("Unable to find " + DisplayName.of(entitySkeleton) + " by " + property + ": " + entitySkeleton.getUuid()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static <T extends BaseEntity<T>> Class<?> entityClass(final T entityOrProxy) { | ||||||
|  |         final var entityClass = entityClass(entityOrProxy.getClass()); | ||||||
|  |         if (entityClass == null) { | ||||||
|  |             throw new IllegalArgumentException("@Entity not found in superclass hierarchy of " + entityOrProxy.getClass()); | ||||||
|  |         } | ||||||
|  |         return entityClass; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static Class<?> entityClass(final Class<?> entityOrProxyClass) { | ||||||
|  |         return entityOrProxyClass.isAnnotationPresent(Entity.class) | ||||||
|  |                 ? entityOrProxyClass | ||||||
|  |                 : entityOrProxyClass.getSuperclass() == null | ||||||
|  |                 ? null | ||||||
|  |                 : entityClass(entityOrProxyClass.getSuperclass()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.rbac.grant; | package net.hostsharing.hsadminng.rbac.grant; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacGrantsApi; | import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacGrantsApi; | ||||||
| import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource; | import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| @@ -22,7 +22,7 @@ public class RbacGrantController implements RbacGrantsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private RbacGrantRepository rbacGrantRepository; |     private RbacGrantRepository rbacGrantRepository; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.rbac.role; | package net.hostsharing.hsadminng.rbac.role; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacRolesApi; | import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacRolesApi; | ||||||
| import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacRoleResource; | import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacRoleResource; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| @@ -18,7 +18,7 @@ public class RbacRoleController implements RbacRolesApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private RbacRoleRepository rbacRoleRepository; |     private RbacRoleRepository rbacRoleRepository; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.rbac.subject; | package net.hostsharing.hsadminng.rbac.subject; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacSubjectsApi; | import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacSubjectsApi; | ||||||
| import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectPermissionResource; | import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectPermissionResource; | ||||||
| import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectResource; | import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectResource; | ||||||
| @@ -21,7 +21,7 @@ public class RbacSubjectController implements RbacSubjectsApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private RbacSubjectRepository rbacSubjectRepository; |     private RbacSubjectRepository rbacSubjectRepository; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.rbac.test.cust; | package net.hostsharing.hsadminng.rbac.test.cust; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.test.generated.api.v1.api.TestCustomersApi; | import net.hostsharing.hsadminng.test.generated.api.v1.api.TestCustomersApi; | ||||||
| import net.hostsharing.hsadminng.test.generated.api.v1.model.TestCustomerResource; | import net.hostsharing.hsadminng.test.generated.api.v1.model.TestCustomerResource; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| @@ -21,7 +21,7 @@ public class TestCustomerController implements TestCustomersApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private TestCustomerRepository testCustomerRepository; |     private TestCustomerRepository testCustomerRepository; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package net.hostsharing.hsadminng.rbac.test.pac; | package net.hostsharing.hsadminng.rbac.test.pac; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.mapper.OptionalFromJson; | import net.hostsharing.hsadminng.mapper.OptionalFromJson; | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.test.generated.api.v1.api.TestPackagesApi; | import net.hostsharing.hsadminng.test.generated.api.v1.api.TestPackagesApi; | ||||||
| @@ -21,7 +21,7 @@ public class TestPackageController implements TestPackagesApi { | |||||||
|     private Context context; |     private Context context; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private Mapper mapper; |     private StandardMapper mapper; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     private TestPackageRepository testPackageRepository; |     private TestPackageRepository testPackageRepository; | ||||||
|   | |||||||
| @@ -1,17 +1,20 @@ | |||||||
| package net.hostsharing.hsadminng.hs.booking.item; | package net.hostsharing.hsadminng.hs.booking.item; | ||||||
|  |  | ||||||
|  | import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration; | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity; | import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity; | ||||||
| import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealRepository; | import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealRepository; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StrictMapper; | ||||||
|  | import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; | ||||||
| import org.junit.jupiter.api.BeforeEach; | import org.junit.jupiter.api.BeforeEach; | ||||||
| import org.junit.jupiter.api.Nested; | import org.junit.jupiter.api.Nested; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| import org.junit.runner.RunWith; | import org.junit.runner.RunWith; | ||||||
| import org.mockito.Mock; |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||||||
|  | import org.springframework.boot.test.context.TestConfiguration; | ||||||
| import org.springframework.boot.test.mock.mockito.MockBean; | import org.springframework.boot.test.mock.mockito.MockBean; | ||||||
|  | import org.springframework.context.annotation.Bean; | ||||||
| import org.springframework.context.annotation.Import; | import org.springframework.context.annotation.Import; | ||||||
| import org.springframework.http.MediaType; | import org.springframework.http.MediaType; | ||||||
| import org.springframework.test.context.junit4.SpringRunner; | import org.springframework.test.context.junit4.SpringRunner; | ||||||
| @@ -28,13 +31,14 @@ import java.util.UUID; | |||||||
| import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; | import static net.hostsharing.hsadminng.rbac.test.JsonMatcher.lenientlyEquals; | ||||||
| import static org.hamcrest.Matchers.matchesRegex; | import static org.hamcrest.Matchers.matchesRegex; | ||||||
| import static org.mockito.ArgumentMatchers.any; | import static org.mockito.ArgumentMatchers.any; | ||||||
|  | import static org.mockito.Mockito.mock; | ||||||
| import static org.mockito.Mockito.when; | import static org.mockito.Mockito.when; | ||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; | ||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | ||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||
|  |  | ||||||
| @WebMvcTest(HsBookingItemController.class) | @WebMvcTest(HsBookingItemController.class) | ||||||
| @Import(Mapper.class) | @Import({StrictMapper.class, JsonObjectMapperConfiguration.class}) | ||||||
| @RunWith(SpringRunner.class) | @RunWith(SpringRunner.class) | ||||||
| class HsBookingItemControllerRestTest { | class HsBookingItemControllerRestTest { | ||||||
|  |  | ||||||
| @@ -44,8 +48,12 @@ class HsBookingItemControllerRestTest { | |||||||
|     @MockBean |     @MockBean | ||||||
|     Context contextMock; |     Context contextMock; | ||||||
|  |  | ||||||
|     @Mock |     @Autowired | ||||||
|     EntityManager em; |     @SuppressWarnings("unused") // not used in test, but in controller class | ||||||
|  |     StrictMapper mapper; | ||||||
|  |  | ||||||
|  |     @MockBean | ||||||
|  |     EntityManagerWrapper em; | ||||||
|  |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     EntityManagerFactory emf; |     EntityManagerFactory emf; | ||||||
| @@ -56,6 +64,16 @@ class HsBookingItemControllerRestTest { | |||||||
|     @MockBean |     @MockBean | ||||||
|     HsBookingItemRbacRepository rbacBookingItemRepo; |     HsBookingItemRbacRepository rbacBookingItemRepo; | ||||||
|  |  | ||||||
|  |     @TestConfiguration | ||||||
|  |     public static class TestConfig { | ||||||
|  |  | ||||||
|  |         @Bean | ||||||
|  |         public EntityManager entityManager() { | ||||||
|  |             return mock(EntityManager.class); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @BeforeEach |     @BeforeEach | ||||||
|     void init() { |     void init() { | ||||||
|         when(emf.createEntityManager()).thenReturn(em); |         when(emf.createEntityManager()).thenReturn(em); | ||||||
|   | |||||||
| @@ -35,7 +35,8 @@ class HsDomainSetupBookingItemValidatorUnitTest { | |||||||
|                 .project(project) |                 .project(project) | ||||||
|                 .caption("Test-Domain") |                 .caption("Test-Domain") | ||||||
|                 .resources(Map.ofEntries( |                 .resources(Map.ofEntries( | ||||||
|                         entry("domainName", "example.org") |                         entry("domainName", "example.org"), | ||||||
|  |                         entry("targetUnixUser", "xyz00") | ||||||
|                 )) |                 )) | ||||||
|                 .build(); |                 .build(); | ||||||
|  |  | ||||||
| @@ -55,6 +56,7 @@ class HsDomainSetupBookingItemValidatorUnitTest { | |||||||
|                 .caption("Test-Domain") |                 .caption("Test-Domain") | ||||||
|                 .resources(Map.ofEntries( |                 .resources(Map.ofEntries( | ||||||
|                         entry("domainName", "example.org"), |                         entry("domainName", "example.org"), | ||||||
|  |                         entry("targetUnixUser", "xyz00"), | ||||||
|                         entry("verificationCode", "1234-5678-9100") |                         entry("verificationCode", "1234-5678-9100") | ||||||
|                 )) |                 )) | ||||||
|                 .build(); |                 .build(); | ||||||
| @@ -73,7 +75,8 @@ class HsDomainSetupBookingItemValidatorUnitTest { | |||||||
|                 .project(project) |                 .project(project) | ||||||
|                 .caption("Test-Domain") |                 .caption("Test-Domain") | ||||||
|                 .resources(Map.ofEntries( |                 .resources(Map.ofEntries( | ||||||
|                         entry("domainName", right(TOO_LONG_DOMAIN_NAME, 253)) |                         entry("domainName", right(TOO_LONG_DOMAIN_NAME, 253)), | ||||||
|  |                         entry("targetUnixUser", "xyz00") | ||||||
|                 )) |                 )) | ||||||
|                 .build(); |                 .build(); | ||||||
|  |  | ||||||
| @@ -91,7 +94,8 @@ class HsDomainSetupBookingItemValidatorUnitTest { | |||||||
|                 .project(project) |                 .project(project) | ||||||
|                 .caption("Test-Domain") |                 .caption("Test-Domain") | ||||||
|                 .resources(Map.ofEntries( |                 .resources(Map.ofEntries( | ||||||
|                         entry("domainName", right(TOO_LONG_DOMAIN_NAME, 254)) |                         entry("domainName", right(TOO_LONG_DOMAIN_NAME, 254)), | ||||||
|  |                         entry("targetUnixUser", "xyz00") | ||||||
|                 )) |                 )) | ||||||
|                 .build(); |                 .build(); | ||||||
|  |  | ||||||
| @@ -102,6 +106,44 @@ class HsDomainSetupBookingItemValidatorUnitTest { | |||||||
|         assertThat(result).contains("'D-12345:Test-Project:Test-Domain.resources.domainName' length is expected to be at max 253 but length of 'dfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.example.org' is 254"); |         assertThat(result).contains("'D-12345:Test-Project:Test-Domain.resources.domainName' length is expected to be at max 253 but length of 'dfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.asdfghijklmnopqrstuvwxyz0123456789.example.org' is 254"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     void acceptsValidUnixUser() { | ||||||
|  |         final var domainSetupBookingItemEntity = HsBookingItemRealEntity.builder() | ||||||
|  |                 .type(DOMAIN_SETUP) | ||||||
|  |                 .project(project) | ||||||
|  |                 .caption("Test-Domain") | ||||||
|  |                 .resources(Map.ofEntries( | ||||||
|  |                         entry("domainName", "example.com"), | ||||||
|  |                         entry("targetUnixUser", "xyz00-test") | ||||||
|  |                 )) | ||||||
|  |                 .build(); | ||||||
|  |  | ||||||
|  |         // when | ||||||
|  |         final var result = HsBookingItemEntityValidatorRegistry.doValidate(em, domainSetupBookingItemEntity); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(result).isEmpty(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Test | ||||||
|  |     void rejectsInvalidUnixUser() { | ||||||
|  |         final var domainSetupBookingItemEntity = HsBookingItemRealEntity.builder() | ||||||
|  |                 .type(DOMAIN_SETUP) | ||||||
|  |                 .project(project) | ||||||
|  |                 .caption("Test-Domain") | ||||||
|  |                 .resources(Map.ofEntries( | ||||||
|  |                         entry("domainName", "example.com"), | ||||||
|  |                         entry("targetUnixUser", "xyz00test") | ||||||
|  |                 )) | ||||||
|  |                 .build(); | ||||||
|  |  | ||||||
|  |         // when | ||||||
|  |         final var result = HsBookingItemEntityValidatorRegistry.doValidate(em, domainSetupBookingItemEntity); | ||||||
|  |  | ||||||
|  |         // then | ||||||
|  |         assertThat(result).contains("'D-12345:Test-Project:Test-Domain.resources.targetUnixUser' = 'xyz00test' is not a valid unix-user name"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @ParameterizedTest |     @ParameterizedTest | ||||||
|     @ValueSource(strings = { |     @ValueSource(strings = { | ||||||
|             "de", "com", "net", "org", "actually-any-top-level-domain", |             "de", "com", "net", "org", "actually-any-top-level-domain", | ||||||
| @@ -123,7 +165,8 @@ class HsDomainSetupBookingItemValidatorUnitTest { | |||||||
|                 .project(project) |                 .project(project) | ||||||
|                 .caption("Test-Domain") |                 .caption("Test-Domain") | ||||||
|                 .resources(Map.ofEntries( |                 .resources(Map.ofEntries( | ||||||
|                         entry("domainName", secondLevelRegistrarDomain) |                         entry("domainName", secondLevelRegistrarDomain), | ||||||
|  |                         entry("targetUnixUser", "xyz00") | ||||||
|                 )) |                 )) | ||||||
|                 .build(); |                 .build(); | ||||||
|  |  | ||||||
| @@ -148,7 +191,8 @@ class HsDomainSetupBookingItemValidatorUnitTest { | |||||||
|                 .project(project) |                 .project(project) | ||||||
|                 .caption("Test-Domain") |                 .caption("Test-Domain") | ||||||
|                 .resources(Map.ofEntries( |                 .resources(Map.ofEntries( | ||||||
|                         entry("domainName", secondLevelRegistrarDomain) |                         entry("domainName", secondLevelRegistrarDomain), | ||||||
|  |                         entry("targetUnixUser", "xyz00") | ||||||
|                 )) |                 )) | ||||||
|                 .build(); |                 .build(); | ||||||
|  |  | ||||||
| @@ -170,6 +214,7 @@ class HsDomainSetupBookingItemValidatorUnitTest { | |||||||
|         // then |         // then | ||||||
|         assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( |         assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( | ||||||
|                 "{type=string, propertyName=domainName, matchesRegEx=[^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}], matchesRegExDescription=is not a (non-top-level) fully qualified domain name, notMatchesRegEx=[[^.]+, (co|org|gov|ac|sch)\\.uk, (com|net|org|edu|gov|asn|id)\\.au, (co|ne|or|ac|go)\\.jp, (com|net|org|gov|edu|ac)\\.cn, (com|net|org|gov|edu|mil|art)\\.br, (co|net|org|gen|firm|ind)\\.in, (com|net|org|gob|edu)\\.mx, (gov|edu)\\.it, (co|net|org|govt|ac|school|geek|kiwi)\\.nz, (co|ne|or|go|re|pe)\\.kr], notMatchesRegExDescription=is a forbidden registrar-level domain name, maxLength=253, required=true, writeOnce=true}", |                 "{type=string, propertyName=domainName, matchesRegEx=[^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}], matchesRegExDescription=is not a (non-top-level) fully qualified domain name, notMatchesRegEx=[[^.]+, (co|org|gov|ac|sch)\\.uk, (com|net|org|edu|gov|asn|id)\\.au, (co|ne|or|ac|go)\\.jp, (com|net|org|gov|edu|ac)\\.cn, (com|net|org|gov|edu|mil|art)\\.br, (co|net|org|gen|firm|ind)\\.in, (com|net|org|gob|edu)\\.mx, (gov|edu)\\.it, (co|net|org|govt|ac|school|geek|kiwi)\\.nz, (co|ne|or|go|re|pe)\\.kr], notMatchesRegExDescription=is a forbidden registrar-level domain name, maxLength=253, required=true, writeOnce=true}", | ||||||
|  |                 "{type=string, propertyName=targetUnixUser, matchesRegEx=[^[a-z][a-z0-9]{2}[0-9]{2}$|^[a-z][a-z0-9]{2}[0-9]{2}-[a-z0-9\\._-]+$], matchesRegExDescription=is not a valid unix-user name, maxLength=253, required=true, writeOnce=true}", | ||||||
|                 "{type=string, propertyName=verificationCode, minLength=12, maxLength=64, computed=IN_INIT}"); |                 "{type=string, propertyName=verificationCode, minLength=12, maxLength=64, computed=IN_INIT}"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.config.JsonObjectMapperConfiguration; | |||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealRepository; | import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealRepository; | ||||||
| import net.hostsharing.hsadminng.mapper.Array; | import net.hostsharing.hsadminng.mapper.Array; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; | import net.hostsharing.hsadminng.persistence.EntityManagerWrapper; | ||||||
| import org.junit.jupiter.api.BeforeEach; | import org.junit.jupiter.api.BeforeEach; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| @@ -52,7 +52,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. | |||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||
|  |  | ||||||
| @WebMvcTest(HsHostingAssetController.class) | @WebMvcTest(HsHostingAssetController.class) | ||||||
| @Import({Mapper.class, JsonObjectMapperConfiguration.class}) | @Import({ StandardMapper.class, JsonObjectMapperConfiguration.class}) | ||||||
| @RunWith(SpringRunner.class) | @RunWith(SpringRunner.class) | ||||||
| public class HsHostingAssetControllerRestTest { | public class HsHostingAssetControllerRestTest { | ||||||
|  |  | ||||||
| @@ -63,10 +63,11 @@ public class HsHostingAssetControllerRestTest { | |||||||
|     Context contextMock; |     Context contextMock; | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
|     Mapper mapper; |     @SuppressWarnings("unused") // not used in test, but in controller class | ||||||
|  |     StandardMapper mapper; | ||||||
|  |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     private EntityManagerWrapper em; |     EntityManagerWrapper em; | ||||||
|  |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     EntityManagerFactory emf; |     EntityManagerFactory emf; | ||||||
| @@ -90,6 +91,7 @@ public class HsHostingAssetControllerRestTest { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     enum ListTestCases { |     enum ListTestCases { | ||||||
|         CLOUD_SERVER( |         CLOUD_SERVER( | ||||||
|                 List.of( |                 List.of( | ||||||
|   | |||||||
| @@ -42,7 +42,8 @@ class HsDomainSetupHostingAssetValidatorUnitTest { | |||||||
|                         .project(project) |                         .project(project) | ||||||
|                         .type(HsBookingItemType.DOMAIN_SETUP) |                         .type(HsBookingItemType.DOMAIN_SETUP) | ||||||
|                         .resources(new HashMap<>(ofEntries( |                         .resources(new HashMap<>(ofEntries( | ||||||
|                                 entry("domainName", domainName) |                                 entry("domainName", domainName), | ||||||
|  |                                 entry("targetUnixUser", "xyz00") | ||||||
|                         )))); |                         )))); | ||||||
|         HsBookingItemEntityValidatorRegistry.forType(HsBookingItemType.DOMAIN_SETUP).prepareProperties(null, bookingItem); |         HsBookingItemEntityValidatorRegistry.forType(HsBookingItemType.DOMAIN_SETUP).prepareProperties(null, bookingItem); | ||||||
|         return HsHostingAssetRbacEntity.builder() |         return HsHostingAssetRbacEntity.builder() | ||||||
|   | |||||||
| @@ -1680,13 +1680,19 @@ public class ImportHostingAssets extends BaseOfficeDataImport { | |||||||
|                 final var relatedProject = domainSetup.getSubHostingAssets().stream() |                 final var relatedProject = domainSetup.getSubHostingAssets().stream() | ||||||
|                         .map(ha -> ha.getAssignedToAsset() != null ? ha.getAssignedToAsset().getRelatedProject() : null) |                         .map(ha -> ha.getAssignedToAsset() != null ? ha.getAssignedToAsset().getRelatedProject() : null) | ||||||
|                         .findAny().orElseThrow(); |                         .findAny().orElseThrow(); | ||||||
|  |                 final var targetUnixUser = domainSetup.getSubHostingAssets().stream() | ||||||
|  |                         .filter(subAsset -> subAsset.getType() == DOMAIN_HTTP_SETUP) | ||||||
|  |                         .map(domainHttpSetup -> domainHttpSetup.getAssignedToAsset().getIdentifier()) | ||||||
|  |                         .findAny().orElse(null); | ||||||
|                 final var bookingItem = HsBookingItemRealEntity.builder() |                 final var bookingItem = HsBookingItemRealEntity.builder() | ||||||
|                         .type(HsBookingItemType.DOMAIN_SETUP) |                         .type(HsBookingItemType.DOMAIN_SETUP) | ||||||
|                         .caption("BI " + domainSetup.getIdentifier()) |                         .caption("BI " + domainSetup.getIdentifier()) | ||||||
|                         .project((HsBookingProjectRealEntity) relatedProject) |                         .project((HsBookingProjectRealEntity) relatedProject) | ||||||
|                         //.validity(toPostgresDateRange(created, cancelled)) |                         //.validity(toPostgresDateRange(created, cancelled)) | ||||||
|                         .resources(Map.ofEntries( |                         .resources(Map.ofEntries( | ||||||
|                                 entry("domainName", domainSetup.getIdentifier()))) |                                 entry("domainName", domainSetup.getIdentifier()), | ||||||
|  |                                 entry("targetUnixUser", targetUnixUser) | ||||||
|  |                         )) | ||||||
|                         .build(); |                         .build(); | ||||||
|                 domainSetup.setBookingItem(bookingItem); |                 domainSetup.setBookingItem(bookingItem); | ||||||
|                 bookingItems.put(nextAvailableBookingItemId(), bookingItem); |                 bookingItems.put(nextAvailableBookingItemId(), bookingItem); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.hs.office.bankaccount; | package net.hostsharing.hsadminng.hs.office.bankaccount; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.junit.jupiter.params.ParameterizedTest; | import org.junit.jupiter.params.ParameterizedTest; | ||||||
| import org.junit.jupiter.params.provider.EnumSource; | import org.junit.jupiter.params.provider.EnumSource; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| @@ -25,7 +25,7 @@ class HsOfficeBankAccountControllerRestTest { | |||||||
|     Context contextMock; |     Context contextMock; | ||||||
|  |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     Mapper mapper; |     StandardMapper mapper; | ||||||
|  |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     HsOfficeBankAccountRepository bankAccountRepo; |     HsOfficeBankAccountRepository bankAccountRepo; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.hs.office.coopassets; | package net.hostsharing.hsadminng.hs.office.coopassets; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.rbac.test.JsonBuilder; | import net.hostsharing.hsadminng.rbac.test.JsonBuilder; | ||||||
| import org.junit.jupiter.params.ParameterizedTest; | import org.junit.jupiter.params.ParameterizedTest; | ||||||
| import org.junit.jupiter.params.provider.EnumSource; | import org.junit.jupiter.params.provider.EnumSource; | ||||||
| @@ -30,7 +30,7 @@ class HsOfficeCoopAssetsTransactionControllerRestTest { | |||||||
|     Context contextMock; |     Context contextMock; | ||||||
|  |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     Mapper mapper; |     StandardMapper mapper; | ||||||
|  |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo; |     HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.hs.office.coopshares; | package net.hostsharing.hsadminng.hs.office.coopshares; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.rbac.test.JsonBuilder; | import net.hostsharing.hsadminng.rbac.test.JsonBuilder; | ||||||
| import org.junit.jupiter.params.ParameterizedTest; | import org.junit.jupiter.params.ParameterizedTest; | ||||||
| import org.junit.jupiter.params.provider.EnumSource; | import org.junit.jupiter.params.provider.EnumSource; | ||||||
| @@ -30,7 +30,7 @@ class HsOfficeCoopSharesTransactionControllerRestTest { | |||||||
|     Context contextMock; |     Context contextMock; | ||||||
|  |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     Mapper mapper; |     StandardMapper mapper; | ||||||
|  |  | ||||||
|     @MockBean |     @MockBean | ||||||
|     HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; |     HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo; | ||||||
|   | |||||||
| @@ -433,7 +433,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu | |||||||
|                     .post("http://localhost/api/hs/office/debitors") |                     .post("http://localhost/api/hs/office/debitors") | ||||||
|                 .then().log().all().assertThat() |                 .then().log().all().assertThat() | ||||||
|                     .statusCode(400) |                     .statusCode(400) | ||||||
|                     .body("message", is("ERROR: [400] Unable to find RealRelation by uuid: 00000000-0000-0000-0000-000000000000")); |                     .body("message", is("ERROR: [400] Unable to find RealRelation by debitorRelUuid: 00000000-0000-0000-0000-000000000000")); | ||||||
|                 // @formatter:on |                 // @formatter:on | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.membership; | |||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository; | import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionRepository; | ||||||
| import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; | import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.junit.jupiter.api.BeforeEach; | import org.junit.jupiter.api.BeforeEach; | ||||||
| import org.junit.jupiter.api.Nested; | import org.junit.jupiter.api.Nested; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| @@ -32,7 +32,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. | |||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||
|  |  | ||||||
| @WebMvcTest(HsOfficeMembershipController.class) | @WebMvcTest(HsOfficeMembershipController.class) | ||||||
| @Import(Mapper.class) | @Import(StandardMapper.class) | ||||||
| public class HsOfficeMembershipControllerRestTest { | public class HsOfficeMembershipControllerRestTest { | ||||||
|  |  | ||||||
|     @Autowired |     @Autowired | ||||||
| @@ -115,7 +115,7 @@ public class HsOfficeMembershipControllerRestTest { | |||||||
|                     .andExpect(status().is4xxClientError()) |                     .andExpect(status().is4xxClientError()) | ||||||
|                     .andExpect(jsonPath("statusCode", is(400))) |                     .andExpect(jsonPath("statusCode", is(400))) | ||||||
|                     .andExpect(jsonPath("statusPhrase", is("Bad Request"))) |                     .andExpect(jsonPath("statusPhrase", is("Bad Request"))) | ||||||
|                     .andExpect(jsonPath("message", is("ERROR: [400] Unable to find Partner by uuid: " + givenPartnerUuid))); |                     .andExpect(jsonPath("message", is("ERROR: [400] Unable to find Partner by partner.uuid: " + givenPartnerUuid))); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         @ParameterizedTest |         @ParameterizedTest | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import io.hypersistence.utils.hibernate.type.range.Range; | |||||||
| import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; | import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; | ||||||
| import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipStatusResource; | import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipStatusResource; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase; | import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase; | ||||||
| import org.junit.jupiter.api.BeforeEach; | import org.junit.jupiter.api.BeforeEach; | ||||||
| import org.junit.jupiter.api.TestInstance; | import org.junit.jupiter.api.TestInstance; | ||||||
| @@ -40,7 +40,7 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase< | |||||||
|     @Mock |     @Mock | ||||||
|     private EntityManager em; |     private EntityManager em; | ||||||
|  |  | ||||||
|     private Mapper mapper = new Mapper(); |     private StandardMapper mapper = new StandardMapper(); | ||||||
|  |  | ||||||
|     @BeforeEach |     @BeforeEach | ||||||
|     void initMocks() { |     void initMocks() { | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity; | |||||||
| import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; | import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; | ||||||
| import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; | import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; | ||||||
| import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; | import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.junit.jupiter.api.BeforeEach; | import org.junit.jupiter.api.BeforeEach; | ||||||
| import org.junit.jupiter.api.Nested; | import org.junit.jupiter.api.Nested; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| @@ -36,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. | |||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||
|  |  | ||||||
| @WebMvcTest(HsOfficePartnerController.class) | @WebMvcTest(HsOfficePartnerController.class) | ||||||
| @Import(Mapper.class) | @Import(StandardMapper.class) | ||||||
| class HsOfficePartnerControllerRestTest { | class HsOfficePartnerControllerRestTest { | ||||||
|  |  | ||||||
|     static final UUID GIVEN_MANDANTE_UUID = UUID.randomUUID(); |     static final UUID GIVEN_MANDANTE_UUID = UUID.randomUUID(); | ||||||
|   | |||||||
| @@ -195,7 +195,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl | |||||||
|                     .post("http://localhost/api/hs/office/sepamandates") |                     .post("http://localhost/api/hs/office/sepamandates") | ||||||
|                 .then().log().all().assertThat() |                 .then().log().all().assertThat() | ||||||
|                     .statusCode(400) |                     .statusCode(400) | ||||||
|                     .body("message", is("ERROR: [400] Unable to find BankAccount by uuid: 00000000-0000-0000-0000-000000000000")); |                     .body("message", is("ERROR: [400] Unable to find BankAccount with uuid 00000000-0000-0000-0000-000000000000")); | ||||||
|             // @formatter:on |             // @formatter:on | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -225,7 +225,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl | |||||||
|                     .post("http://localhost/api/hs/office/sepamandates") |                     .post("http://localhost/api/hs/office/sepamandates") | ||||||
|                 .then().log().all().assertThat() |                 .then().log().all().assertThat() | ||||||
|                     .statusCode(400) |                     .statusCode(400) | ||||||
|                     .body("message", is("ERROR: [400] Unable to find Debitor by uuid: 00000000-0000-0000-0000-000000000000")); |                     .body("message", is("ERROR: [400] Unable to find Debitor with uuid 00000000-0000-0000-0000-000000000000")); | ||||||
|                 // @formatter:on |                 // @formatter:on | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.rbac.context; | package net.hostsharing.hsadminng.rbac.context; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import net.hostsharing.hsadminng.mapper.Array; | import net.hostsharing.hsadminng.mapper.Array; | ||||||
| import net.hostsharing.hsadminng.rbac.test.JpaAttempt; | import net.hostsharing.hsadminng.rbac.test.JpaAttempt; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| @@ -17,7 +17,7 @@ import jakarta.servlet.http.HttpServletRequest; | |||||||
| import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||||
|  |  | ||||||
| @DataJpaTest | @DataJpaTest | ||||||
| @ComponentScan(basePackageClasses = { Context.class, JpaAttempt.class, Mapper.class }) | @ComponentScan(basePackageClasses = { Context.class, JpaAttempt.class, StandardMapper.class }) | ||||||
| @DirtiesContext | @DirtiesContext | ||||||
| class ContextIntegrationTests { | class ContextIntegrationTests { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.rbac.role; | package net.hostsharing.hsadminng.rbac.role; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.junit.jupiter.api.BeforeEach; | import org.junit.jupiter.api.BeforeEach; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| import org.junit.runner.RunWith; | import org.junit.runner.RunWith; | ||||||
| @@ -30,7 +30,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. | |||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||
|  |  | ||||||
| @WebMvcTest(RbacRoleController.class) | @WebMvcTest(RbacRoleController.class) | ||||||
| @Import(Mapper.class) | @Import(StandardMapper.class) | ||||||
| @RunWith(SpringRunner.class) | @RunWith(SpringRunner.class) | ||||||
| class RbacRoleControllerRestTest { | class RbacRoleControllerRestTest { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package net.hostsharing.hsadminng.rbac.subject; | package net.hostsharing.hsadminng.rbac.subject; | ||||||
|  |  | ||||||
| import net.hostsharing.hsadminng.context.Context; | import net.hostsharing.hsadminng.context.Context; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.junit.jupiter.api.BeforeEach; | import org.junit.jupiter.api.BeforeEach; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| import org.junit.runner.RunWith; | import org.junit.runner.RunWith; | ||||||
| @@ -31,7 +31,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. | |||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||||||
|  |  | ||||||
| @WebMvcTest(RbacSubjectController.class) | @WebMvcTest(RbacSubjectController.class) | ||||||
| @Import(Mapper.class) | @Import(StandardMapper.class) | ||||||
| @RunWith(SpringRunner.class) | @RunWith(SpringRunner.class) | ||||||
| class RbacSubjectControllerRestTest { | class RbacSubjectControllerRestTest { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.rbac.test; | |||||||
|  |  | ||||||
| import lombok.*; | import lombok.*; | ||||||
| import net.hostsharing.hsadminng.errors.DisplayAs; | import net.hostsharing.hsadminng.errors.DisplayAs; | ||||||
| import net.hostsharing.hsadminng.mapper.Mapper; | import net.hostsharing.hsadminng.mapper.StandardMapper; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| import org.junit.jupiter.api.extension.ExtendWith; | import org.junit.jupiter.api.extension.ExtendWith; | ||||||
| import org.mockito.InjectMocks; | import org.mockito.InjectMocks; | ||||||
| @@ -27,7 +27,7 @@ class MapperUnitTest { | |||||||
|     EntityManager em; |     EntityManager em; | ||||||
|  |  | ||||||
|     @InjectMocks |     @InjectMocks | ||||||
|     Mapper mapper; |     StandardMapper mapper; | ||||||
|  |  | ||||||
|     final UUID GIVEN_UUID = UUID.randomUUID(); |     final UUID GIVEN_UUID = UUID.randomUUID(); | ||||||
|  |  | ||||||
| @@ -50,7 +50,7 @@ class MapperUnitTest { | |||||||
|     @Test |     @Test | ||||||
|     void mapsBeanWithExistingSubEntity() { |     void mapsBeanWithExistingSubEntity() { | ||||||
|         final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s1(new SubSourceBean1(GIVEN_UUID)).build(); |         final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s1(new SubSourceBean1(GIVEN_UUID)).build(); | ||||||
|         when(em.find(SubTargetBean1.class, GIVEN_UUID)).thenReturn(new SubTargetBean1(GIVEN_UUID, "xxx")); |         when(em.getReference(SubTargetBean1.class, GIVEN_UUID)).thenReturn(new SubTargetBean1(GIVEN_UUID, "xxx")); | ||||||
|  |  | ||||||
|         final var result = mapper.map(givenSource, TargetBean.class); |         final var result = mapper.map(givenSource, TargetBean.class); | ||||||
|         assertThat(result).usingRecursiveComparison().isEqualTo( |         assertThat(result).usingRecursiveComparison().isEqualTo( | ||||||
| @@ -81,27 +81,27 @@ class MapperUnitTest { | |||||||
|     @Test |     @Test | ||||||
|     void mapsBeanWithSubEntityNotFound() { |     void mapsBeanWithSubEntityNotFound() { | ||||||
|         final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s1(new SubSourceBean1(GIVEN_UUID)).build(); |         final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s1(new SubSourceBean1(GIVEN_UUID)).build(); | ||||||
|         when(em.find(SubTargetBean1.class, GIVEN_UUID)).thenReturn(null); |         when(em.getReference(SubTargetBean1.class, GIVEN_UUID)).thenReturn(null); | ||||||
|  |  | ||||||
|         final var exception = catchThrowable(() -> |         final var exception = catchThrowable(() -> | ||||||
|                 mapper.map(givenSource, TargetBean.class) |                 mapper.map(givenSource, TargetBean.class) | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         assertThat(exception).isInstanceOf(ValidationException.class) |         assertThat(exception).isInstanceOf(ValidationException.class) | ||||||
|                 .hasMessage("Unable to find SubTargetBean1 by uuid: " + GIVEN_UUID); |                 .hasMessage("Unable to find SubTargetBean1 by s1.uuid: " + GIVEN_UUID); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     void mapsBeanWithSubEntityNotFoundAndDisplayName() { |     void mapsBeanWithSubEntityNotFoundAndDisplayName() { | ||||||
|         final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s2(new SubSourceBean2(GIVEN_UUID)).build(); |         final SourceBean givenSource = SourceBean.builder().a("1234").b("Text").s2(new SubSourceBean2(GIVEN_UUID)).build(); | ||||||
|         when(em.find(SubTargetBean2.class, GIVEN_UUID)).thenReturn(null); |         when(em.getReference(SubTargetBean2.class, GIVEN_UUID)).thenReturn(null); | ||||||
|  |  | ||||||
|         final var exception = catchThrowable(() -> |         final var exception = catchThrowable(() -> | ||||||
|                 mapper.map(givenSource, TargetBean.class) |                 mapper.map(givenSource, TargetBean.class) | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         assertThat(exception).isInstanceOf(ValidationException.class) |         assertThat(exception).isInstanceOf(ValidationException.class) | ||||||
|                 .hasMessage("Unable to find SomeDisplayName by uuid: " + GIVEN_UUID); |                 .hasMessage("Unable to find SomeDisplayName by s2.uuid: " + GIVEN_UUID); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user