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; 017 018import static java.util.Objects.requireNonNull; 019 020import java.util.List; 021import java.util.Optional; 022 023import de.cuioss.test.valueobjects.api.TestContract; 024import de.cuioss.test.valueobjects.api.contracts.VerifyBeanProperty; 025import de.cuioss.test.valueobjects.objects.ParameterizedInstantiator; 026import de.cuioss.test.valueobjects.objects.RuntimeProperties; 027import de.cuioss.test.valueobjects.objects.impl.BeanInstantiator; 028import de.cuioss.test.valueobjects.objects.impl.DefaultInstantiator; 029import de.cuioss.test.valueobjects.property.PropertyMetadata; 030import de.cuioss.test.valueobjects.property.PropertySupport; 031import de.cuioss.test.valueobjects.util.AnnotationHelper; 032import de.cuioss.tools.logging.CuiLogger; 033import de.cuioss.tools.property.PropertyReadWrite; 034import lombok.Getter; 035import lombok.NonNull; 036import lombok.RequiredArgsConstructor; 037 038/** 039 * Tests all given properties according to the given List of 040 * {@link PropertyMetadata} 041 * 042 * @author Oliver Wolff 043 * @param <T> Rule does not apply to annotations: There is no inheritance 044 */ 045@RequiredArgsConstructor 046public class BeanPropertyContractImpl<T> implements TestContract<T> { 047 048 private static final CuiLogger log = new CuiLogger(BeanPropertyContractImpl.class); 049 050 @Getter 051 @NonNull 052 private final ParameterizedInstantiator<T> instantiator; 053 054 @Override 055 public void assertContract() { 056 final var builder = new StringBuilder("Verifying "); 057 builder.append(getClass().getName()).append("\nWith configuration: ").append(getInstantiator().toString()); 058 log.info(builder.toString()); 059 060 checkGetterAndSetterContract(); 061 checkDefaultContract(); 062 } 063 064 private void checkGetterAndSetterContract() { 065 final var readWriteProperties = getInstantiator().getRuntimeProperties().getAllProperties().stream() 066 .filter(p -> PropertyReadWrite.READ_WRITE.equals(p.getPropertyReadWrite())).toList(); 067 068 if (readWriteProperties.isEmpty()) { 069 log.warn( 070 "There are no properties defined that are readable and writable. Consider your configuration and/or the base class for your test."); 071 } else { 072 log.info("Verifying properties that are Read and Write: " 073 + RuntimeProperties.extractNames(readWriteProperties)); 074 075 final var supportList = readWriteProperties.stream().map(PropertySupport::new).toList(); 076 final Object target = getInstantiator().newInstanceMinimal(); 077 for (final PropertySupport support : supportList) { 078 079 support.generateTestValue(); 080 081 support.apply(target); 082 083 support.assertValueSet(target); 084 } 085 } 086 } 087 088 private void checkDefaultContract() { 089 final var defaultProperties = getInstantiator().getRuntimeProperties().getDefaultProperties(); 090 if (defaultProperties.isEmpty()) { 091 log.debug("No default properties configured"); 092 } else { 093 final var defaultPropertySupport = defaultProperties.stream().map(PropertySupport::new).toList(); 094 final Object target = getInstantiator().newInstanceMinimal(); 095 for (final PropertySupport support : defaultPropertySupport) { 096 support.assertDefaultValue(target); 097 } 098 } 099 } 100 101 /** 102 * Factory method for creating an instance of {@link BeanPropertyContractImpl} 103 * depending on the given parameter 104 * 105 * @param beanType identifying the type to be tested. Must not be 106 * null and must provide a no args public 107 * constructor 108 * @param annotated the annotated unit-test-class. It is expected 109 * to be annotated with 110 * {@link BeanPropertyContractImpl}, otherwise 111 * the method will return 112 * {@link Optional#empty()} 113 * @param initialPropertyMetadata identifying the complete set of 114 * {@link PropertyMetadata}, where the actual 115 * {@link PropertyMetadata} for the bean tests 116 * will be filtered by using the attributes 117 * defined within 118 * {@link BeanPropertyContractImpl}. Must not be 119 * null. If it is empty the method will return 120 * {@link Optional#empty()} 121 * @return an instance Of {@link BeanPropertyContractImpl} in case all 122 * requirements for the parameters are correct, otherwise it will return 123 * {@link Optional#empty()} 124 */ 125 public static final <T> Optional<TestContract<T>> createBeanPropertyTestContract(final Class<T> beanType, 126 final Class<?> annotated, final List<PropertyMetadata> initialPropertyMetadata) { 127 128 requireNonNull(beanType, "beantype must not be null"); 129 requireNonNull(annotated, "annotated must not be null"); 130 requireNonNull(initialPropertyMetadata, "initialPropertyMetadata must not be null"); 131 132 if (!annotated.isAnnotationPresent(VerifyBeanProperty.class)) { 133 log.debug("No annotation of type BeanPropertyTestContract available on class: " + annotated); 134 return Optional.empty(); 135 } 136 if (initialPropertyMetadata.isEmpty()) { 137 log.warn("No configured properties found to be tested, offending class: " + annotated); 138 return Optional.empty(); 139 } 140 final var instantiator = new DefaultInstantiator<>(beanType); 141 final var metadata = AnnotationHelper.handleMetadataForPropertyTest(annotated, initialPropertyMetadata); 142 143 return Optional.of( 144 new BeanPropertyContractImpl<>(new BeanInstantiator<>(instantiator, new RuntimeProperties(metadata)))); 145 } 146}