diff --git a/src/main/java/com/deepoove/swagger/diff/compare/ModelDiff.java b/src/main/java/com/deepoove/swagger/diff/compare/ModelDiff.java index a77445ce..d5f122b4 100644 --- a/src/main/java/com/deepoove/swagger/diff/compare/ModelDiff.java +++ b/src/main/java/com/deepoove/swagger/diff/compare/ModelDiff.java @@ -11,9 +11,13 @@ import com.deepoove.swagger.diff.model.ElProperty; +import io.swagger.models.ArrayModel; import io.swagger.models.Model; +import io.swagger.models.RefModel; +import io.swagger.models.properties.ArrayProperty; import io.swagger.models.properties.Property; import io.swagger.models.properties.RefProperty; +import io.swagger.models.properties.StringProperty; /** * compare two model @@ -51,12 +55,18 @@ public ModelDiff diff(Model leftModel, Model rightModel, String parentEl) { return this.diff(leftModel, rightModel, parentEl, new HashSet()); } - private ModelDiff diff(Model leftModel, Model rightModel, String parentEl, Set visited) { + public ModelDiff diff(Property leftProperty, Property rightProperty) { + return this.diff(findModel(leftProperty, oldDedinitions), findModel(rightProperty, newDedinitions)); + } + + private ModelDiff diff(Model leftInputModel, Model rightInputModel, String parentEl, Set visited) { // Stop recursing if both models are null // OR either model is already contained in the visiting history - if ((null == leftModel && null == rightModel) || visited.contains(leftModel) || visited.contains(rightModel)) { + if ((null == leftInputModel && null == rightInputModel) || visited.contains(leftInputModel) || visited.contains(rightInputModel)) { return this; } + Model leftModel = isModelReference(leftInputModel) ? findReferenceModel(leftInputModel, oldDedinitions) : leftInputModel; + Model rightModel = isModelReference(rightInputModel) ? findReferenceModel(rightInputModel, newDedinitions) : rightInputModel; Map leftProperties = null == leftModel ? null : leftModel.getProperties(); Map rightProperties = null == rightModel ? null : rightModel.getProperties(); @@ -71,19 +81,16 @@ private ModelDiff diff(Model leftModel, Model rightModel, String parentEl, Set { Property left = leftProperties.get(key); Property right = rightProperties.get(key); - - if ((left instanceof RefProperty) && (right instanceof RefProperty)) { - String leftRef = ((RefProperty) left).getSimpleRef(); - String rightRef = ((RefProperty) right).getSimpleRef(); - - diff(oldDedinitions.get(leftRef), newDedinitions.get(rightRef), + Model leftSubModel = findModel(left, oldDedinitions); + Model rightSubModel = findModel(left, newDedinitions); + if (leftSubModel != null || rightSubModel != null) { + diff(leftSubModel, rightSubModel, buildElString(parentEl, key), copyAndAdd(visited, leftModel, rightModel)); - } else if (left != null && right != null && !left.equals(right)) { // Add a changed ElProperty if not a Reference // Useless - changed.add(convert2ElProperty(key, parentEl, left)); + changed.add(addChangeMetadata(convert2ElProperty(key, parentEl, left), left, right)); } }); return this; @@ -113,6 +120,23 @@ private ElProperty convert2ElProperty(String propName, String parentEl, Property return pWithPath; } + private ElProperty addChangeMetadata(ElProperty diffProperty, Property left, Property right) { + diffProperty.setTypeChange(!left.getType().equalsIgnoreCase(right.getType())); + List leftEnums = enumValues(left); + List rightEnums = enumValues(right); + if (!leftEnums.isEmpty() && !rightEnums.isEmpty()) { + ListDiff enumDiff = ListDiff.diff(leftEnums, rightEnums, (t, enumVal) -> { + for (String value : t) { + if (enumVal.equalsIgnoreCase(value)) { return value; } + } + return null; + }); + diffProperty.setNewEnums(enumDiff.getIncreased() != null && ! enumDiff.getIncreased().isEmpty()); + diffProperty.setRemovedEnums(enumDiff.getMissing() != null && ! enumDiff.getMissing().isEmpty()); + } + return diffProperty; + } + @SuppressWarnings("unchecked") private Set copyAndAdd(Set set, T... add) { Set newSet = new HashSet(set); @@ -120,6 +144,45 @@ private Set copyAndAdd(Set set, T... add) { return newSet; } + private List enumValues(Property prop) { + if (prop instanceof StringProperty && ((StringProperty) prop).getEnum() != null) { + return ((StringProperty) prop).getEnum(); + } else { + return new ArrayList<>(); + } + } + + private Model findModel(Property property, Map modelMap) { + String modelName = null; + if (property instanceof RefProperty){ + modelName = ((RefProperty) property).getSimpleRef(); + } else if (property instanceof ArrayProperty) { + Property arrayType = ((ArrayProperty) property).getItems(); + if (arrayType instanceof RefProperty) { + modelName = ((RefProperty) arrayType).getSimpleRef(); + } + } + return modelName == null ? null : modelMap.get(modelName); + } + + private boolean isModelReference(Model model) { + return model instanceof RefModel || model instanceof ArrayModel; + } + + private Model findReferenceModel(Model model, Map modelMap) { + String modelName = null; + if (model instanceof RefModel){ + modelName = ((RefModel) model).getSimpleRef(); + } else if (model instanceof ArrayModel) { + Property arrayType = ((ArrayModel) model).getItems(); + if (arrayType instanceof RefProperty) { + modelName = ((RefProperty) arrayType).getSimpleRef(); + } + } + return modelName == null ? null : modelMap.get(modelName); + } + + public List getIncreased() { return increased; } diff --git a/src/main/java/com/deepoove/swagger/diff/compare/ParameterDiff.java b/src/main/java/com/deepoove/swagger/diff/compare/ParameterDiff.java index a8716ba4..d2425ecc 100644 --- a/src/main/java/com/deepoove/swagger/diff/compare/ParameterDiff.java +++ b/src/main/java/com/deepoove/swagger/diff/compare/ParameterDiff.java @@ -9,7 +9,6 @@ import com.deepoove.swagger.diff.model.ChangedParameter; import io.swagger.models.Model; -import io.swagger.models.RefModel; import io.swagger.models.parameters.BodyParameter; import io.swagger.models.parameters.Parameter; @@ -65,19 +64,12 @@ public ParameterDiff diff(List left, List right) { BodyParameter leftBodyPara = (BodyParameter) leftPara; Model leftSchema = leftBodyPara.getSchema(); BodyParameter rightBodyPara = (BodyParameter) rightPara; - Model rightSchema = rightBodyPara.getSchema(); - if (leftSchema instanceof RefModel && rightSchema instanceof RefModel) { - String leftRef = ((RefModel) leftSchema).getSimpleRef(); - String rightRef = ((RefModel) rightSchema).getSimpleRef(); - Model leftModel = oldDedinitions.get(leftRef); - Model rightModel = newDedinitions.get(rightRef); - - ModelDiff diff = ModelDiff.buildWithDefinition(oldDedinitions, newDedinitions) - .diff(leftModel, rightModel, leftPara.getName()); - changedParameter.setIncreased(diff.getIncreased()); - changedParameter.setMissing(diff.getMissing()); - changedParameter.setChanged(diff.getChanged()); - } + Model rightSchema = rightBodyPara.getSchema(); + ModelDiff diff = ModelDiff.buildWithDefinition(oldDedinitions, newDedinitions) + .diff(leftSchema, rightSchema, leftPara.getName()); + changedParameter.setIncreased(diff.getIncreased()); + changedParameter.setMissing(diff.getMissing()); + changedParameter.setChanged(diff.getChanged()); } // is requried diff --git a/src/main/java/com/deepoove/swagger/diff/compare/PropertyDiff.java b/src/main/java/com/deepoove/swagger/diff/compare/PropertyDiff.java index 9a549bec..4edb1d38 100644 --- a/src/main/java/com/deepoove/swagger/diff/compare/PropertyDiff.java +++ b/src/main/java/com/deepoove/swagger/diff/compare/PropertyDiff.java @@ -8,7 +8,6 @@ import io.swagger.models.Model; import io.swagger.models.properties.Property; -import io.swagger.models.properties.RefProperty; public class PropertyDiff { @@ -34,16 +33,12 @@ public static PropertyDiff buildWithDefinition(Map left, } public PropertyDiff diff(Property left, Property right) { - if ((null == left || left instanceof RefProperty) && (null == right || right instanceof RefProperty)) { - Model leftModel = null == left ? null : oldDedinitions.get(((RefProperty) left).getSimpleRef()); - Model rightModel = null == right ? null : newDedinitions.get(((RefProperty) right).getSimpleRef()); - ModelDiff diff = ModelDiff - .buildWithDefinition(oldDedinitions, newDedinitions) - .diff(leftModel, rightModel); - increased.addAll(diff.getIncreased()); - missing.addAll(diff.getMissing()); - changed.addAll(diff.getChanged()); - } + ModelDiff diff = ModelDiff + .buildWithDefinition(oldDedinitions, newDedinitions) + .diff(left, right); + increased.addAll(diff.getIncreased()); + missing.addAll(diff.getMissing()); + changed.addAll(diff.getChanged()); return this; } diff --git a/src/main/java/com/deepoove/swagger/diff/compare/SpecificationDiff.java b/src/main/java/com/deepoove/swagger/diff/compare/SpecificationDiff.java index e3c6854d..c8af282c 100644 --- a/src/main/java/com/deepoove/swagger/diff/compare/SpecificationDiff.java +++ b/src/main/java/com/deepoove/swagger/diff/compare/SpecificationDiff.java @@ -90,6 +90,16 @@ public static SpecificationDiff diff(Swagger oldSpec, Swagger newSpec) { changedOperation.setMissingProps(propertyDiff.getMissing()); changedOperation.setChangedProps(propertyDiff.getChanged()); + // Diff Consumes + ListDiff consumeDiff = getMediaTypeDiff(oldOperation.getConsumes(), newOperation.getConsumes()); + changedOperation.setAddConsumes(consumeDiff.getIncreased()); + changedOperation.setMissingConsumes(consumeDiff.getMissing()); + + // Diff Produces + ListDiff producesDiff = getMediaTypeDiff(oldOperation.getProduces(), newOperation.getProduces()); + changedOperation.setAddProduces(producesDiff.getIncreased()); + changedOperation.setMissingProduces(producesDiff.getMissing()); + if (changedOperation.isDiff()) { operas.put(method, changedOperation); } @@ -151,6 +161,15 @@ private static Collection convert2EndpointList(String pathUr return endpoints; } + private static ListDiff getMediaTypeDiff(List oldTypes, List newTypes) { + return ListDiff.diff(oldTypes, newTypes, (t, sample) -> { + for (String mediaType : t) { + if (sample.equalsIgnoreCase(mediaType)) { return mediaType; } + } + return null; + }); + } + public List getNewEndpoints() { return newEndpoints; } diff --git a/src/main/java/com/deepoove/swagger/diff/model/ChangedOperation.java b/src/main/java/com/deepoove/swagger/diff/model/ChangedOperation.java index 2ace1686..fc56b3d6 100644 --- a/src/main/java/com/deepoove/swagger/diff/model/ChangedOperation.java +++ b/src/main/java/com/deepoove/swagger/diff/model/ChangedOperation.java @@ -17,6 +17,10 @@ public class ChangedOperation implements Changed { private List addProps = new ArrayList(); private List missingProps = new ArrayList(); private List changedProps = new ArrayList(); + private List addConsumes = new ArrayList<>(); + private List missingConsumes = new ArrayList<>(); + private List addProduces = new ArrayList<>(); + private List missingProduces = new ArrayList<>(); public List getAddParameters() { return addParameters; @@ -76,7 +80,7 @@ public void setSummary(String summary) { public boolean isDiff() { return !addParameters.isEmpty() || !missingParameters.isEmpty() - || !changedParameter.isEmpty() || isDiffProp(); + || !changedParameter.isEmpty() || isDiffProp() || isDiffConsumes() || isDiffProduces(); } public boolean isDiffProp(){ return !addProps.isEmpty() @@ -87,4 +91,44 @@ public boolean isDiffParam(){ return !addParameters.isEmpty() || !missingParameters.isEmpty() || !changedParameter.isEmpty(); } + + public boolean isDiffConsumes(){ + return !addConsumes.isEmpty() || !missingConsumes.isEmpty(); + } + + public boolean isDiffProduces(){ + return !addProduces.isEmpty() || !missingProduces.isEmpty(); + } + + public List getAddConsumes() { + return this.addConsumes; + } + + public void setAddConsumes(List increased) { + this.addConsumes = increased == null ? new ArrayList<>() : increased; + } + + public List getMissingConsumes() { + return this.missingConsumes; + } + + public void setMissingConsumes(List missing) { + this.missingConsumes = missing == null ? new ArrayList<>() : missing; + } + + public List getAddProduces() { + return this.addProduces; + } + + public void setAddProduces(List increased) { + this.addProduces = increased == null ? new ArrayList<>() : increased; + } + + public List getMissingProduces() { + return this.missingProduces; + } + + public void setMissingProduces(List missing) { + this.missingProduces = missing == null ? new ArrayList<>() : missing; + } } diff --git a/src/main/java/com/deepoove/swagger/diff/model/ElProperty.java b/src/main/java/com/deepoove/swagger/diff/model/ElProperty.java index 807c3ceb..d8748376 100644 --- a/src/main/java/com/deepoove/swagger/diff/model/ElProperty.java +++ b/src/main/java/com/deepoove/swagger/diff/model/ElProperty.java @@ -13,6 +13,11 @@ public class ElProperty { private Property property; + // optional change metadata + private boolean isTypeChange; + private boolean newEnums; + private boolean removedEnums; + public Property getProperty() { return property; } @@ -29,4 +34,27 @@ public void setEl(String el) { this.el = el; } + public boolean isTypeChange() { + return isTypeChange; + } + + public void setTypeChange(boolean typeChange) { + isTypeChange = typeChange; + } + + public boolean isNewEnums() { + return newEnums; + } + + public void setNewEnums(boolean newEnums) { + this.newEnums = newEnums; + } + + public boolean isRemovedEnums() { + return removedEnums; + } + + public void setRemovedEnums(boolean removedEnums) { + this.removedEnums = removedEnums; + } } diff --git a/src/main/java/com/deepoove/swagger/diff/output/HtmlRender.java b/src/main/java/com/deepoove/swagger/diff/output/HtmlRender.java index 1b8af372..d7cb699c 100644 --- a/src/main/java/com/deepoove/swagger/diff/output/HtmlRender.java +++ b/src/main/java/com/deepoove/swagger/diff/output/HtmlRender.java @@ -7,6 +7,7 @@ import io.swagger.models.properties.Property; import j2html.tags.ContainerTag; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -125,6 +126,12 @@ private ContainerTag ol_changed(List changedEndpoints) { if (changedOperation.isDiffProp()) { ul_detail.with(li().with(h3("Return Type")).with(ul_response(changedOperation))); } + if (changedOperation.isDiffProduces()) { + ul_detail.with(li().with(h3("Produces")).with(ul_produce(changedOperation))); + } + if (changedOperation.isDiffConsumes()) { + ul_detail.with(li().with(h3("Consumes")).with(ul_consume(changedOperation))); + } ol.with(li().with(span(method).withClass(method)).withText(pathUrl + " ").with(span(null == desc ? "" : desc)) .with(ul_detail)); } @@ -135,6 +142,7 @@ private ContainerTag ol_changed(List changedEndpoints) { private ContainerTag ul_response(ChangedOperation changedOperation) { List addProps = changedOperation.getAddProps(); List delProps = changedOperation.getMissingProps(); + List chgProps = changedOperation.getChangedProps(); ContainerTag ul = ul().withClass("change response"); for (ElProperty prop : addProps) { ul.with(li_addProp(prop)); @@ -142,6 +150,9 @@ private ContainerTag ul_response(ChangedOperation changedOperation) { for (ElProperty prop : delProps) { ul.with(li_missingProp(prop)); } + for (ElProperty prop : chgProps) { + ul.with(li_changedProp(prop)); + } return ul; } @@ -155,6 +166,24 @@ private ContainerTag li_addProp(ElProperty prop) { return li().withText("Add " + prop.getEl()).with(span(null == property.getDescription() ? "" : ("//" + property.getDescription())).withClass("comment")); } + private ContainerTag li_changedProp(ElProperty prop) { + List changeDetails = new ArrayList<>(); + String changeDetailsHeading = ""; + if (prop.isTypeChange()) { + changeDetails.add("Data Type"); + } + if (prop.isNewEnums()) { + changeDetails.add("Added Enum"); + } + if (prop.isRemovedEnums()) { + changeDetails.add("Removed Enum"); + } + if (! changeDetails.isEmpty()) { + changeDetailsHeading = " (" + String.join(", ", changeDetails) + ")"; + } + return li().withText("Change " + prop.getEl()).with(span(changeDetailsHeading).withClass("comment")); + } + private ContainerTag ul_param(ChangedOperation changedOperation) { List addParameters = changedOperation.getAddParameters(); List delParameters = changedOperation.getMissingParameters(); @@ -181,6 +210,12 @@ private ContainerTag ul_param(ChangedOperation changedOperation) { ul.with(li_missingProp(prop)); } } + for (ChangedParameter param : changedParameters) { + List changed = param.getChanged(); + for (ElProperty prop : changed) { + ul.with(li_changedProp(prop)); + } + } for (Parameter param : delParameters) { ul.with(li_missingParam(param)); } @@ -210,4 +245,37 @@ private ContainerTag li_changedParam(ChangedParameter changeParam) { return li; } + private ContainerTag ul_produce(ChangedOperation changedOperation) { + List addProduce = changedOperation.getAddProduces(); + List delProduce = changedOperation.getMissingProduces(); + ContainerTag ul = ul().withClass("change produces"); + for (String mt : addProduce) { + ul.with(li_addMediaType(mt)); + } + for (String mt : delProduce) { + ul.with(li_missingMediaType(mt)); + } + return ul; + } + + private ContainerTag ul_consume(ChangedOperation changedOperation) { + List addConsume = changedOperation.getAddConsumes(); + List delConsume = changedOperation.getMissingConsumes(); + ContainerTag ul = ul().withClass("change consumes"); + for (String mt : addConsume) { + ul.with(li_addMediaType(mt)); + } + for (String mt : delConsume) { + ul.with(li_missingMediaType(mt)); + } + return ul; + } + + private ContainerTag li_missingMediaType(String type) { + return li().withClass("missing").withText("Delete").with(del(type)).with(span("")); + } + + private ContainerTag li_addMediaType(String type) { + return li().withText("Add " + type).with(span("")); + } } diff --git a/src/main/java/com/deepoove/swagger/diff/output/MarkdownRender.java b/src/main/java/com/deepoove/swagger/diff/output/MarkdownRender.java index d9572208..08a6bdf6 100644 --- a/src/main/java/com/deepoove/swagger/diff/output/MarkdownRender.java +++ b/src/main/java/com/deepoove/swagger/diff/output/MarkdownRender.java @@ -5,11 +5,7 @@ import java.util.Map.Entry; import com.deepoove.swagger.diff.SwaggerDiff; -import com.deepoove.swagger.diff.model.ChangedEndpoint; -import com.deepoove.swagger.diff.model.ChangedOperation; -import com.deepoove.swagger.diff.model.ChangedParameter; -import com.deepoove.swagger.diff.model.Endpoint; -import com.deepoove.swagger.diff.model.ElProperty; +import com.deepoove.swagger.diff.model.*; import io.swagger.models.HttpMethod; import io.swagger.models.parameters.Parameter; @@ -103,6 +99,14 @@ private String ol_changed(List changedEndpoints) { ul_detail.append(PRE_LI).append("Return Type") .append(ul_response(changedOperation)); } + if (changedOperation.isDiffProduces()) { + ul_detail.append(PRE_LI).append("Produces") + .append(ul_produce(changedOperation)); + } + if (changedOperation.isDiffConsumes()) { + ul_detail.append(PRE_LI).append("Consumes") + .append(ul_consume(changedOperation)); + } sb.append(CODE).append(method).append(CODE) .append(" " + pathUrl).append(" " + desc + " \n") .append(ul_detail); @@ -235,4 +239,45 @@ private String li_changedParam(ChangedParameter changeParam) { return sb.toString(); } + private String ul_produce(ChangedOperation changedOperation) { + List addProduce = changedOperation.getAddProduces(); + List delProduce = changedOperation.getMissingProduces(); + StringBuffer sb = new StringBuffer("\n\n"); + + String prefix = PRE_LI + PRE_CODE; + for (String mt : addProduce) { + sb.append(PRE_LI).append(PRE_CODE).append(li_addMediaType(mt) + "\n"); + } + for (String mt : delProduce) { + sb.append(prefix).append(li_missingMediaType(mt) + "\n"); + } + return sb.toString(); + } + + private String ul_consume(ChangedOperation changedOperation) { + List addConsume = changedOperation.getAddConsumes(); + List delConsume = changedOperation.getMissingConsumes(); + StringBuffer sb = new StringBuffer("\n\n"); + + String prefix = PRE_LI + PRE_CODE; + for (String mt : addConsume) { + sb.append(PRE_LI).append(PRE_CODE).append(li_addMediaType(mt) + "\n"); + } + for (String mt : delConsume) { + sb.append(prefix).append(li_missingMediaType(mt) + "\n"); + } + return sb.toString(); + } + + private String li_missingMediaType(String type) { + StringBuffer sb = new StringBuffer(""); + sb.append("Delete ").append(type); + return sb.toString(); + } + + private String li_addMediaType(String type) { + StringBuffer sb = new StringBuffer(""); + sb.append("Insert ").append(type); + return sb.toString(); + } } diff --git a/src/test/java/com/deepoove/swagger/test/SwaggerDiffTest.java b/src/test/java/com/deepoove/swagger/test/SwaggerDiffTest.java index 760a7d60..6405ab59 100644 --- a/src/test/java/com/deepoove/swagger/test/SwaggerDiffTest.java +++ b/src/test/java/com/deepoove/swagger/test/SwaggerDiffTest.java @@ -1,18 +1,22 @@ package com.deepoove.swagger.test; import com.deepoove.swagger.diff.SwaggerDiff; -import com.deepoove.swagger.diff.model.ChangedEndpoint; -import com.deepoove.swagger.diff.model.Endpoint; +import com.deepoove.swagger.diff.model.*; import com.deepoove.swagger.diff.output.HtmlRender; import com.deepoove.swagger.diff.output.JsonRender; import com.deepoove.swagger.diff.output.MarkdownRender; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +import io.swagger.models.HttpMethod; +import io.swagger.models.parameters.BodyParameter; import org.junit.Assert; import org.junit.Test; import java.io.*; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; @@ -190,6 +194,114 @@ public void testJsonRender() { } } + @Test + public void testInputBodyArray() { + SwaggerDiff diff = SwaggerDiff.compareV2(SWAGGER_V2_DOC1, SWAGGER_V2_DOC2); + Map changedEndpointMap = diff.getChangedEndpoints().stream().collect(Collectors.toMap(ChangedEndpoint::getPathUrl, e -> e)); + Lists.newArrayList("/user/createWithArray", "/user/createWithList").forEach(name -> { + Assert.assertTrue("Expecting changed endpoint " + name, changedEndpointMap.containsKey(name)); + ChangedEndpoint endpoint = changedEndpointMap.get(name); + Assert.assertEquals(1, endpoint.getChangedOperations().size()); + Assert.assertTrue("Expecting POST method change", endpoint.getChangedOperations().containsKey(HttpMethod.POST)); + Assert.assertEquals(0, endpoint.getChangedOperations().get(HttpMethod.POST).getMissingParameters().size()); + Assert.assertEquals(0, endpoint.getChangedOperations().get(HttpMethod.POST).getAddParameters().size()); + Assert.assertEquals(1, endpoint.getChangedOperations().get(HttpMethod.POST).getChangedParameter().size()); + + // assert changed property counts + ChangedParameter changedInput = endpoint.getChangedOperations().get(HttpMethod.POST).getChangedParameter().get(0); + Assert.assertTrue(changedInput.getLeftParameter() instanceof BodyParameter); + Assert.assertTrue(changedInput.getRightParameter() instanceof BodyParameter); + Assert.assertEquals(3, changedInput.getIncreased().size()); + Assert.assertEquals(3, changedInput.getMissing().size()); + Assert.assertEquals(1, changedInput.getChanged().size()); + + // assert embedded array change is one of the missing properties + List missingProperties = changedInput.getMissing(); + Set elementPaths = missingProperties.stream().map(ElProperty::getEl).collect(Collectors.toSet()); + Assert.assertTrue(elementPaths.contains("body.favorite.tags.removedField")); + }); + } + + @Test + public void testResponseBodyArray() { + SwaggerDiff diff = SwaggerDiff.compareV2(SWAGGER_V2_DOC1, SWAGGER_V2_DOC2); + Map changedEndpointMap = diff.getChangedEndpoints().stream().collect(Collectors.toMap(ChangedEndpoint::getPathUrl, e -> e)); + Lists.newArrayList("/pet/findByStatus", "/pet/findByTags").forEach(name -> { + Assert.assertTrue("Expecting changed endpoint " + name, changedEndpointMap.containsKey(name)); + ChangedEndpoint endpoint = changedEndpointMap.get(name); + Assert.assertEquals(1, endpoint.getChangedOperations().size()); + Assert.assertTrue("Expecting GET method change", endpoint.getChangedOperations().containsKey(HttpMethod.GET)); + + // assert changed property counts + ChangedOperation changedOutput = endpoint.getChangedOperations().get(HttpMethod.GET); + Assert.assertEquals(3, changedOutput.getAddProps().size()); + Assert.assertEquals(3, changedOutput.getMissingProps().size()); + Assert.assertEquals(1, changedOutput.getChangedProps().size()); + + // assert embedded array change is one of the missing properties + List missingProperties =changedOutput.getMissingProps(); + Set elementPaths = missingProperties.stream().map(ElProperty::getEl).collect(Collectors.toSet()); + Assert.assertTrue(elementPaths.contains("tags.removedField")); + }); + } + + @Test + public void testDetectProducesAndConsumes() { + SwaggerDiff diff = SwaggerDiff.compareV2(SWAGGER_V2_DOC1, SWAGGER_V2_DOC2); + Map changedEndpointMap = diff.getChangedEndpoints().stream().collect(Collectors.toMap(ChangedEndpoint::getPathUrl, e -> e)); + Assert.assertTrue("Expecting changed endpoint " + "/store/order", changedEndpointMap.containsKey("/store/order")); + ChangedEndpoint endpoint = changedEndpointMap.get("/store/order"); + Assert.assertTrue("Expecting POST method change", endpoint.getChangedOperations().containsKey(HttpMethod.POST)); + ChangedOperation changedOperation = endpoint.getChangedOperations().get(HttpMethod.POST); + Assert.assertEquals(1, changedOperation.getAddConsumes().size()); + Assert.assertEquals(1, changedOperation.getMissingConsumes().size()); + Assert.assertEquals(0, changedOperation.getAddProduces().size()); + Assert.assertEquals(1, changedOperation.getMissingProduces().size()); + } + + @Test + public void testChangedPropertyMetadata() { + SwaggerDiff diff = SwaggerDiff.compareV2(SWAGGER_V2_DOC1, SWAGGER_V2_DOC2); + Map changedEndpointMap = diff.getChangedEndpoints().stream().collect(Collectors.toMap(ChangedEndpoint::getPathUrl, e -> e)); + String postOrder = "/store/order"; + String getOrder = "/store/order/{orderId}"; + + Assert.assertTrue("Expecting changed endpoint " + postOrder, changedEndpointMap.containsKey(postOrder)); + ChangedEndpoint postOrderChg = changedEndpointMap.get(postOrder); + ChangedOperation postOrderChgOp = postOrderChg.getChangedOperations().get(HttpMethod.POST); + Assert.assertEquals(1, postOrderChgOp.getChangedParameter().size()); + + List postChgProps = postOrderChgOp.getChangedParameter().get(0).getChanged(); + Assert.assertEquals(2, postChgProps.size()); + ElProperty orderIdProp = postChgProps.stream().filter(cp -> { + return cp.getEl().equalsIgnoreCase("body.id");}).findFirst().get(); + Assert.assertTrue(orderIdProp.isTypeChange()); + Assert.assertFalse(orderIdProp.isNewEnums()); + Assert.assertFalse(orderIdProp.isRemovedEnums()); + ElProperty statusProp = postChgProps.stream().filter(cp -> { + return cp.getEl().equalsIgnoreCase("body.status");}).findFirst().get(); + Assert.assertFalse(statusProp.isTypeChange()); + Assert.assertTrue(statusProp.isNewEnums()); + Assert.assertTrue(statusProp.isRemovedEnums()); + + + Assert.assertTrue("Expecting changed endpoint " + getOrder, changedEndpointMap.containsKey(getOrder)); + ChangedEndpoint getOrderChg = changedEndpointMap.get(getOrder); + ChangedOperation getOrderChgOp = getOrderChg.getChangedOperations().get(HttpMethod.GET); + List getChgProps = getOrderChgOp.getChangedProps(); + Assert.assertEquals(2, getChgProps.size()); + + orderIdProp = getChgProps.stream().filter(cp -> { + return cp.getEl().equalsIgnoreCase("id");}).findFirst().get(); + Assert.assertTrue(orderIdProp.isTypeChange()); + Assert.assertFalse(orderIdProp.isNewEnums()); + Assert.assertFalse(orderIdProp.isRemovedEnums()); + statusProp = getChgProps.stream().filter(cp -> { + return cp.getEl().equalsIgnoreCase("status");}).findFirst().get(); + Assert.assertFalse(statusProp.isTypeChange()); + Assert.assertTrue(statusProp.isNewEnums()); + Assert.assertTrue(statusProp.isRemovedEnums()); + } private void assertEqual(SwaggerDiff diff) { List newEndpoints = diff.getNewEndpoints(); diff --git a/src/test/resources/petstore_v2_1.json b/src/test/resources/petstore_v2_1.json index cb49b219..4d4e6b40 100644 --- a/src/test/resources/petstore_v2_1.json +++ b/src/test/resources/petstore_v2_1.json @@ -427,6 +427,9 @@ "summary": "Place an order for a pet", "description": "", "operationId": "placeOrder", + "consumes": [ + "application/xml" + ], "produces": [ "application/xml", "application/json" @@ -916,6 +919,9 @@ }, "name": { "type": "string" + }, + "removedField": { + "type": "string" } }, "xml": { diff --git a/src/test/resources/petstore_v2_2.json b/src/test/resources/petstore_v2_2.json index e528dcf8..29b43d91 100644 --- a/src/test/resources/petstore_v2_2.json +++ b/src/test/resources/petstore_v2_2.json @@ -433,8 +433,10 @@ "summary": "Place an order for a pet", "description": "", "operationId": "placeOrder", + "consumes": [ + "application/json" + ], "produces": [ - "application/xml", "application/json" ], "parameters": [ @@ -820,8 +822,7 @@ "type": "object", "properties": { "id": { - "type": "integer", - "format": "int64" + "type": "string" }, "petId": { "type": "integer", @@ -840,7 +841,7 @@ "description": "Order Status", "enum": [ "placed", - "approved", + "authorized", "delivered" ] },