diff --git a/.gitignore b/.gitignore index 7da72d7f..cef607c2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ .project *.iml +# Maven configuration +.mvn/maven.config + # Mobile Tools for Java (J2ME) .mtj.tmp/ diff --git a/core/src/test/java/org/openapitools/openapidiff/core/ChangesResolver.java b/core/src/test/java/org/openapitools/openapidiff/core/ChangesResolver.java index cb0fe37b..94dac20d 100644 --- a/core/src/test/java/org/openapitools/openapidiff/core/ChangesResolver.java +++ b/core/src/test/java/org/openapitools/openapidiff/core/ChangesResolver.java @@ -110,4 +110,32 @@ public static ChangedSchema getRequestBodyChangedSchema( .map(ChangedMediaType::getSchema) .orElse(null); } + + /** + * Get the response ChangedSchema for the given method, path, response code, and media type. + * + * @param changedOpenApi the ChangedOpenApi object + * @param method the HTTP method + * @param path the path + * @param responseCode the response code + * @param mediaType the media type + * @return the ChangedSchema object + */ + @Nullable + public static ChangedSchema getResponseBodyChangedSchema( + ChangedOpenApi changedOpenApi, + HttpMethod method, + String path, + String responseCode, + String mediaType) { + return Optional.ofNullable(getChangedOperation(changedOpenApi, method, path)) + .map(ChangedOperation::getApiResponses) + .map(ChangedApiResponse::getChanged) + .map(responses -> responses.get(responseCode)) + .map(ChangedResponse::getContent) + .map(ChangedContent::getChanged) + .map(changedMediaTypes -> changedMediaTypes.get(mediaType)) + .map(ChangedMediaType::getSchema) + .orElse(null); + } } diff --git a/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java b/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java index 17014e86..f57729e3 100644 --- a/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java +++ b/core/src/test/java/org/openapitools/openapidiff/core/SchemaDiffTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.openapitools.openapidiff.core.ChangesResolver.getRequestBodyChangedSchema; +import static org.openapitools.openapidiff.core.ChangesResolver.getResponseBodyChangedSchema; import static org.openapitools.openapidiff.core.TestUtils.assertOpenApiBackwardCompatible; import java.io.ByteArrayOutputStream; @@ -15,6 +16,8 @@ import org.junit.jupiter.api.Test; import org.openapitools.openapidiff.core.model.ChangedOpenApi; import org.openapitools.openapidiff.core.model.ChangedSchema; +import org.openapitools.openapidiff.core.model.DiffResult; +import org.openapitools.openapidiff.core.model.schema.ChangedRequired; import org.openapitools.openapidiff.core.output.ConsoleRender; public class SchemaDiffTest { @@ -130,7 +133,6 @@ public void changeMultipleOfHandling() { assertThat(props.get("field3").getMultipleOf().getRight()).isEqualTo(BigDecimal.valueOf(10)); // Check deletion of multipleOf - assertThat(props.get("field4").getMultipleOf().isCompatible()).isTrue(); assertThat(props.get("field4").getMultipleOf().getLeft()).isEqualTo(BigDecimal.valueOf(10)); assertThat(props.get("field4").getMultipleOf().getRight()).isNull(); } @@ -273,4 +275,46 @@ public void changeMinMaxPropertiesHandling() { assertThat(props.get("field4").getMaxProperties().getOldValue()).isEqualTo(100); assertThat(props.get("field4").getMaxProperties().getNewValue()).isEqualTo(10); } + + @Test // issue #346 + public void requiredPropertyChangedTest() { + ChangedOpenApi changedOpenApi = + OpenApiCompare.fromLocations( + "schemaDiff/required-property-1.yaml", "schemaDiff/required-property-2.yaml"); + + // Test request body schema changes + ChangedSchema requestSchema = + getRequestBodyChangedSchema(changedOpenApi, POST, "/schema/required", "application/json"); + + assertNotNull(requestSchema, "Request schema should not be null"); + ChangedRequired requestRequired = requestSchema.getRequired(); + assertNotNull(requestRequired, "Request required changes should not be null"); + + // Test request property changes + assertThat(requestRequired.getIncreased()).contains("becomesRequired"); + assertThat(requestRequired.getMissing()).contains("becomesOptional"); + assertThat(requestRequired.getIncreased()).doesNotContain("unchanged"); + assertThat(requestRequired.getMissing()).doesNotContain("unchanged"); + + // Verify breaking changes in request - new required properties are breaking + assertThat(requestRequired.isItemsChanged()).isEqualTo(DiffResult.INCOMPATIBLE); + + // Test response schema changes + ChangedSchema responseSchema = + getResponseBodyChangedSchema( + changedOpenApi, POST, "/schema/required", "200", "application/json"); + + assertNotNull(responseSchema, "Response schema should not be null"); + ChangedRequired responseRequired = responseSchema.getRequired(); + assertNotNull(responseRequired, "Response required changes should not be null"); + + // Test response property changes + assertThat(responseRequired.getIncreased()).contains("becomesRequired"); + assertThat(responseRequired.getMissing()).contains("becomesOptional"); + assertThat(responseRequired.getIncreased()).doesNotContain("unchanged"); + assertThat(responseRequired.getMissing()).doesNotContain("unchanged"); + + // Verify breaking changes in response - removing required properties is breaking + assertThat(responseRequired.isItemsChanged()).isEqualTo(DiffResult.INCOMPATIBLE); + } } diff --git a/core/src/test/resources/schemaDiff/required-property-1.yaml b/core/src/test/resources/schemaDiff/required-property-1.yaml new file mode 100644 index 00000000..5a1cb48b --- /dev/null +++ b/core/src/test/resources/schemaDiff/required-property-1.yaml @@ -0,0 +1,39 @@ +openapi: 3.0.0 +info: + title: Required Property Test API + version: 1.0.0 +paths: + /schema/required: + post: + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [becomesOptional, unchanged] + properties: + becomesRequired: + type: string + description: Property that changes from optional to required (breaking for requests) + becomesOptional: + type: string + description: Property that changes from required to optional (breaking for responses) + unchanged: + type: string + description: Property that remains unchanged in terms of required status + responses: + '200': + description: Success response + content: + application/json: + schema: + type: object + required: [becomesOptional, unchanged] + properties: + becomesRequired: + type: string + becomesOptional: + type: string + unchanged: + type: string \ No newline at end of file diff --git a/core/src/test/resources/schemaDiff/required-property-2.yaml b/core/src/test/resources/schemaDiff/required-property-2.yaml new file mode 100644 index 00000000..7a865eea --- /dev/null +++ b/core/src/test/resources/schemaDiff/required-property-2.yaml @@ -0,0 +1,39 @@ +openapi: 3.0.0 +info: + title: Required Property Test API + version: 1.0.0 +paths: + /schema/required: + post: + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [becomesRequired, unchanged] + properties: + becomesRequired: + type: string + description: Property that changes from optional to required (breaking for requests) + becomesOptional: + type: string + description: Property that changes from required to optional (breaking for responses) + unchanged: + type: string + description: Property that remains unchanged in terms of required status + responses: + '200': + description: Success response + content: + application/json: + schema: + type: object + required: [becomesRequired, unchanged] + properties: + becomesRequired: + type: string + becomesOptional: + type: string + unchanged: + type: string \ No newline at end of file