001/* 002 * Copyright 2023 the original author or authors. 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.cuioss.test.valueobjects.contract.support; 017 018import static org.junit.jupiter.api.Assertions.assertTrue; 019 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Set; 028import java.util.stream.Collectors; 029 030import de.cuioss.test.valueobjects.api.VerifyMapperConfiguration; 031import de.cuioss.test.valueobjects.objects.RuntimeProperties; 032import de.cuioss.tools.logging.CuiLogger; 033import lombok.EqualsAndHashCode; 034import lombok.ToString; 035 036/** 037 * Helper class for asserting single-attribute mappings for 038 * {@link VerifyMapperConfiguration} 039 * 040 * @author Oliver Wolff 041 * 042 */ 043@EqualsAndHashCode 044@ToString 045public class MapperAttributesAsserts { 046 047 private static final CuiLogger log = new CuiLogger(MapperAttributesAsserts.class); 048 049 private static final String PROPERTY_MAPPING_INCOMPLETE = """ 050 Caution: you have unmapped {}-properties: {} you can adapt this behavior by either:\ 051 052 - @VerifyMapperConfiguration(equals("name:firstName"))\ 053 054 - Make use of the provided property controls like @VerifyMapperConfiguration(of("name"))"""; 055 056 private final List<AssertTuple> sourceAsserts; 057 058 /** 059 * @param config 060 * @param targetProperties 061 * @param sourceProperties 062 */ 063 public MapperAttributesAsserts(VerifyMapperConfiguration config, RuntimeProperties targetProperties, 064 RuntimeProperties sourceProperties) { 065 var targetPropertyMap = targetProperties.asMapView(false); 066 var sourcePropertyMap = sourceProperties.asMapView(false); 067 List<MappingTuple> mapping = new ArrayList<>(MappingAssertStrategy.EQUALS.readConfiguration(config)); 068 069 mapping.addAll(MappingAssertStrategy.NOT_NULL.readConfiguration(config)); 070 071 for (MappingTuple tuple : mapping) { 072 assertTrue(targetPropertyMap.containsKey(tuple.getTarget()), 073 "Invalid (unmapped) attribute-name for target: " + tuple.toString()); 074 assertTrue(sourcePropertyMap.containsKey(tuple.getSource()), 075 "Invalid (unmapped) attribute-name for source: " + tuple.toString()); 076 } 077 078 sourceAsserts = new ArrayList<>(); 079 for (MappingTuple tuple : mapping) { 080 sourceAsserts.add(new AssertTuple(sourcePropertyMap.get(tuple.getSource()), 081 targetPropertyMap.get(tuple.getTarget()), tuple)); 082 } 083 logConfigurationStatus(targetProperties, sourceProperties); 084 } 085 086 private void logConfigurationStatus(RuntimeProperties targetProperties, RuntimeProperties sourceProperties) { 087 if (sourceAsserts.isEmpty()) { 088 log.warn( 089 "No attribute specific mapping found. use @VerifyMapperConfiguration(equals(\"name:firstName\")) or @VerifyMapperConfiguration(notNull(\"name:lastName\")) in order to activate"); 090 } 091 Set<String> sourceMappingNames = sourceAsserts.stream().map(a -> a.getMappingTuple().getSource()) 092 .collect(Collectors.toSet()); 093 Set<String> targetMappingNames = sourceAsserts.stream().map(a -> a.getMappingTuple().getTarget()) 094 .collect(Collectors.toSet()); 095 var sourceTypeProperties = RuntimeProperties.extractNames(sourceProperties.getAllProperties()); 096 var targetTypeProperties = RuntimeProperties.extractNames(targetProperties.getAllProperties()); 097 098 sourceTypeProperties.removeAll(sourceMappingNames); 099 targetTypeProperties.removeAll(targetMappingNames); 100 if (sourceTypeProperties.isEmpty()) { 101 log.info("All source-properties are covered."); 102 } else { 103 log.warn(PROPERTY_MAPPING_INCOMPLETE, "source", sourceTypeProperties); 104 } 105 if (targetTypeProperties.isEmpty()) { 106 log.info("All target-properties are covered."); 107 } else { 108 log.warn(PROPERTY_MAPPING_INCOMPLETE, "target", targetTypeProperties); 109 } 110 } 111 112 /** 113 * @param sourceAttributes to be tested 114 * @param source 115 * @param target 116 */ 117 public void assertMappingForSourceAttributes(Collection<String> sourceAttributes, Object source, Object target) { 118 if (sourceAsserts.isEmpty()) { 119 return; 120 } 121 Map<String, List<AssertTuple>> asserts = new HashMap<>(); 122 for (String name : sourceAttributes) { 123 var concreteAsserts = sourceAsserts.stream().filter(a -> a.isResponsibleForSource(name)).toList(); 124 if (concreteAsserts.isEmpty()) { 125 log.info("Checked property '{}' is not configured to be asserted, ist this intentional?", name); 126 } else { 127 asserts.put(name, concreteAsserts); 128 } 129 } 130 if (asserts.isEmpty()) { 131 return; 132 } 133 // Defines the elements that should not be affected in the correct instance and 134 // should 135 // therefore not be changed 136 Set<AssertTuple> activeAsserts = new HashSet<>(); 137 asserts.values().forEach(activeAsserts::addAll); 138 List<AssertTuple> nullAsserts = new ArrayList<>(sourceAsserts); 139 nullAsserts.removeAll(activeAsserts); 140 141 // Now do the actual checks 142 for (Entry<String, List<AssertTuple>> entry : asserts.entrySet()) { 143 log.debug("Asserting attribute {}", entry.getKey()); 144 entry.getValue().forEach(a -> a.assertContract(source, target)); 145 } 146 // All not affected elements that provide no default should be null / empty 147 for (AssertTuple nullAssert : nullAsserts) { 148 log.debug("Asserting attribute to be null / not set {}", nullAssert.getTargetSupport().getName()); 149 MappingAssertStrategy.NULL_OR_DEFAULT.assertMapping(nullAssert.getSourceSupport(), source, 150 nullAssert.getTargetSupport(), target); 151 } 152 153 } 154 155}