|
24 | 24 | import java.util.HashMap; |
25 | 25 | import java.util.List; |
26 | 26 | import java.util.Map; |
| 27 | +import java.util.stream.Collectors; |
27 | 28 | import org.junit.jupiter.api.Test; |
28 | 29 | import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; |
29 | 30 | import software.amazon.awssdk.codegen.model.intermediate.ListModel; |
@@ -118,6 +119,95 @@ void validate_mapValueWithDanglingTarget_throwsIdentifyingContainerMember() { |
118 | 119 | .hasMessageContaining("MissingValue"); |
119 | 120 | } |
120 | 121 |
|
| 122 | + @Test |
| 123 | + void validate_multipleDanglingMembers_aggregatesAllIntoSingleException() { |
| 124 | + MemberModel enumMember = new MemberModel() |
| 125 | + .withC2jName("AuthorizationDetails") |
| 126 | + .withC2jShape("AuthDetailType") |
| 127 | + .withVariable(new VariableModel("authorizationDetails", "String")) |
| 128 | + .withEnumType("AuthDetailType"); |
| 129 | + ShapeModel enumOwner = shape("ResponseShape"); |
| 130 | + enumOwner.setMembers(Arrays.asList(enumMember)); |
| 131 | + |
| 132 | + MemberModel structureMember = new MemberModel() |
| 133 | + .withC2jName("Nested") |
| 134 | + .withC2jShape("MissingStruct") |
| 135 | + .withVariable(new VariableModel("nested", "MissingStruct")); |
| 136 | + ShapeModel structOwner = shape("RequestShape"); |
| 137 | + structOwner.setMembers(Arrays.asList(structureMember)); |
| 138 | + |
| 139 | + Map<String, ShapeModel> shapes = new HashMap<>(); |
| 140 | + shapes.put("ResponseShape", enumOwner); |
| 141 | + shapes.put("RequestShape", structOwner); |
| 142 | + IntermediateModel model = new IntermediateModel(); |
| 143 | + model.setShapes(shapes); |
| 144 | + |
| 145 | + assertThatThrownBy(() -> MemberShapeTargetValidator.validate(model)) |
| 146 | + .isInstanceOf(ModelInvalidException.class) |
| 147 | + .matches(t -> allUnknownShapeMemberDanger(t, 2), "two UNKNOWN_SHAPE_MEMBER / DANGER entries") |
| 148 | + .matches(t -> entryMessages(t).stream().anyMatch(m -> m.contains("ResponseShape") |
| 149 | + && m.contains("AuthorizationDetails") |
| 150 | + && m.contains("AuthDetailType")), |
| 151 | + "entry for the enum member names its own shape/member/target") |
| 152 | + .matches(t -> entryMessages(t).stream().anyMatch(m -> m.contains("RequestShape") |
| 153 | + && m.contains("Nested") |
| 154 | + && m.contains("MissingStruct")), |
| 155 | + "entry for the structure member names its own shape/member/target"); |
| 156 | + } |
| 157 | + |
| 158 | + @Test |
| 159 | + void validate_mapKeyAndValueBothDangling_reportsDistinctEntryPerTarget() { |
| 160 | + MemberModel keyMember = new MemberModel() |
| 161 | + .withC2jName("key") |
| 162 | + .withC2jShape("MissingKey") |
| 163 | + .withVariable(new VariableModel("key", "MissingKey")); |
| 164 | + MemberModel valueMember = new MemberModel() |
| 165 | + .withC2jName("value") |
| 166 | + .withC2jShape("MissingValue") |
| 167 | + .withVariable(new VariableModel("value", "MissingValue")); |
| 168 | + MemberModel mapMember = new MemberModel() |
| 169 | + .withC2jName("Attributes") |
| 170 | + .withC2jShape("AttributeMap") |
| 171 | + .withVariable(new VariableModel("attributes", "java.util.Map")) |
| 172 | + .withMapModel(new MapModel("java.util.HashMap", "java.util.Map", "key", keyMember, "value", valueMember)); |
| 173 | + IntermediateModel model = modelWithShape("MapContainerShape", mapMember); |
| 174 | + |
| 175 | + assertThatThrownBy(() -> MemberShapeTargetValidator.validate(model)) |
| 176 | + .isInstanceOf(ModelInvalidException.class) |
| 177 | + .matches(t -> allUnknownShapeMemberDanger(t, 2), "two UNKNOWN_SHAPE_MEMBER / DANGER entries") |
| 178 | + .matches(t -> entryMessages(t).stream().anyMatch(m -> m.contains("MissingKey")), "key target reported") |
| 179 | + .matches(t -> entryMessages(t).stream().anyMatch(m -> m.contains("MissingValue")), "value target reported"); |
| 180 | + } |
| 181 | + |
| 182 | + @Test |
| 183 | + void validate_sameTargetReachableTwice_reportedOnce() { |
| 184 | + MemberModel innerKey = new MemberModel() |
| 185 | + .withC2jName("key") |
| 186 | + .withC2jShape("MissingShared") |
| 187 | + .withVariable(new VariableModel("key", "MissingShared")); |
| 188 | + MemberModel innerValue = new MemberModel() |
| 189 | + .withC2jName("value") |
| 190 | + .withC2jShape("MissingShared") |
| 191 | + .withVariable(new VariableModel("value", "MissingShared")); |
| 192 | + MemberModel mapElement = new MemberModel() |
| 193 | + .withC2jName("member") |
| 194 | + .withC2jShape("SharedMap") |
| 195 | + .withVariable(new VariableModel("member", "java.util.Map")) |
| 196 | + .withMapModel(new MapModel("java.util.HashMap", "java.util.Map", "key", innerKey, "value", innerValue)); |
| 197 | + MemberModel listMember = new MemberModel() |
| 198 | + .withC2jName("Items") |
| 199 | + .withC2jShape("ItemList") |
| 200 | + .withVariable(new VariableModel("items", "java.util.List")) |
| 201 | + .withListModel(new ListModel("java.util.Map", null, "java.util.ArrayList", "java.util.List", mapElement)); |
| 202 | + IntermediateModel model = modelWithShape("ListOfMapShape", listMember); |
| 203 | + |
| 204 | + assertThatThrownBy(() -> MemberShapeTargetValidator.validate(model)) |
| 205 | + .isInstanceOf(ModelInvalidException.class) |
| 206 | + .matches(t -> allUnknownShapeMemberDanger(t, 1), "single deduped UNKNOWN_SHAPE_MEMBER / DANGER entry") |
| 207 | + .matches(t -> entryMessages(t).get(0).contains("Items") && entryMessages(t).get(0).contains("MissingShared"), |
| 208 | + "the single entry names the container member and shared target"); |
| 209 | + } |
| 210 | + |
121 | 211 | @Test |
122 | 212 | void validate_scalarMembersWithNullShape_doesNotThrow() { |
123 | 213 | MemberModel scalarMember = new MemberModel() |
@@ -187,9 +277,19 @@ private static ShapeModel shape(String name) { |
187 | 277 | } |
188 | 278 |
|
189 | 279 | private static boolean isUnknownShapeMemberDanger(Throwable t) { |
| 280 | + return allUnknownShapeMemberDanger(t, 1); |
| 281 | + } |
| 282 | + |
| 283 | + private static boolean allUnknownShapeMemberDanger(Throwable t, int expectedCount) { |
190 | 284 | List<ValidationEntry> entries = ((ModelInvalidException) t).validationEntries(); |
191 | | - return entries.size() == 1 |
192 | | - && entries.get(0).getErrorId() == ValidationErrorId.UNKNOWN_SHAPE_MEMBER |
193 | | - && entries.get(0).getSeverity() == ValidationErrorSeverity.DANGER; |
| 285 | + return entries.size() == expectedCount |
| 286 | + && entries.stream().allMatch(e -> e.getErrorId() == ValidationErrorId.UNKNOWN_SHAPE_MEMBER |
| 287 | + && e.getSeverity() == ValidationErrorSeverity.DANGER); |
| 288 | + } |
| 289 | + |
| 290 | + private static List<String> entryMessages(Throwable t) { |
| 291 | + return ((ModelInvalidException) t).validationEntries().stream() |
| 292 | + .map(ValidationEntry::getDetailMessage) |
| 293 | + .collect(Collectors.toList()); |
194 | 294 | } |
195 | 295 | } |
0 commit comments