From fce10c728479e2b9af92287629a3b28a8f8fe068 Mon Sep 17 00:00:00 2001 From: David Hutchison Date: Sun, 15 Aug 2021 09:04:57 +0100 Subject: [PATCH] fix: correctly checks the hash file when using a classpath input spec (#9840) The skipIfSpecIsUnchanged did not work when the input spec came from a classpath resource, which could lead to infinite build loops when the plugin was used in eclipse #5805 --- .../codegen/plugin/CodeGenMojo.java | 2 +- .../codegen/plugin/CodeGenMojoTest.java | 94 ++- .../src/test/resources/classpath/pom.xml | 59 ++ .../test/resources/petstore-on-classpath.yaml | 736 ++++++++++++++++++ 4 files changed, 884 insertions(+), 7 deletions(-) create mode 100644 modules/openapi-generator-maven-plugin/src/test/resources/classpath/pom.xml create mode 100644 modules/openapi-generator-maven-plugin/src/test/resources/petstore-on-classpath.yaml diff --git a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java index 8949156f4d8..1f1dbd946f5 100644 --- a/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java +++ b/modules/openapi-generator-maven-plugin/src/main/java/org/openapitools/codegen/plugin/CodeGenMojo.java @@ -458,7 +458,7 @@ public class CodeGenMojo extends AbstractMojo { } } - if (Boolean.TRUE.equals(skipIfSpecIsUnchanged) && inputSpecFile.exists()) { + if (Boolean.TRUE.equals(skipIfSpecIsUnchanged)) { File storedInputSpecHashFile = getHashFile(inputSpecFile); if (storedInputSpecHashFile.exists()) { String inputSpecHash = null; diff --git a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java index ad69be7e720..dd2083d017e 100644 --- a/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java +++ b/modules/openapi-generator-maven-plugin/src/test/java/org/openapitools/codegen/plugin/CodeGenMojoTest.java @@ -16,6 +16,13 @@ package org.openapitools.codegen.plugin; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; + import org.apache.commons.io.FileUtils; import org.apache.maven.execution.DefaultMavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest; @@ -25,12 +32,6 @@ import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuilder; import org.apache.maven.project.ProjectBuildingRequest; import org.eclipse.aether.DefaultRepositorySystemSession; -import org.openapitools.codegen.plugin.stubs.StubUtility; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; public class CodeGenMojoTest extends BaseTestCase { @Override @@ -67,6 +68,87 @@ public class CodeGenMojoTest extends BaseTestCase { assertTrue(hashFolder.resolve("petstore.yaml-executionId.sha256").toFile().exists()); } + /** + * For a Pom file which refers to a input file which will be on the classpath, as opposed to a file path, + * test that the spec is not regenerated when the hash has not changed. + * + * @throws Exception + */ + public void testSkipRegenerationForClasspathSpecFileNoChange() throws Exception { + + //GIVEN + /* Setup the mojo */ + final Path folder = Files.createTempDirectory("test-classpath"); + final CodeGenMojo mojo = loadMojo(folder.toFile(), "src/test/resources/classpath", "executionId"); + + /* Perform an initial generation */ + mojo.execute(); + + /* Check the hash file was created */ + final Path hashFolder = folder.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); + assertTrue(hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256").toFile().exists()); + + /* Remove the generated source */ + Files.walk(folder.resolve("target/generated-sources/common-maven/remote-openapi/src")) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + + + // WHEN + /* Execute the mojo again */ + mojo.execute(); + + // THEN + /* Verify that the source directory has not been repopulated. If it has then we generated code again */ + assertFalse("src directory should not have been regenerated", + folder.resolve("target/generated-sources/common-maven/remote-openapi/src").toFile().exists()); + + } + + /** + * For a Pom file which refers to a input file which will be on the classpath, as opposed to a file path, + * test that the generated source is regenerated when the hash has changed. + * + * @throws Exception + */ + public void testSkipRegenerationForClasspathSpecFileWithChange() throws Exception { + + //GIVEN + /* Setup the mojo */ + final Path folder = Files.createTempDirectory("test-classpath"); + final CodeGenMojo mojo = loadMojo(folder.toFile(), "src/test/resources/classpath", "executionId"); + + /* Perform an initial generation */ + mojo.execute(); + + /* Check the hash file was created, proving a generation occurred */ + final Path hashFolder = folder.resolve("target/generated-sources/common-maven/remote-openapi/.openapi-generator"); + assertTrue(hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256").toFile().exists()); + + /* Update the hash contents to be a different value, simulating a spec change */ + Files.write( + hashFolder.resolve("petstore-on-classpath.yaml-executionId.sha256"), + Arrays.asList("bd1bf4a953c858f9d47b67ed6029daacf1707e5cbd3d2e4b01383ba30363366f")); + + /* Remove the generated source */ + Files.walk(folder.resolve("target/generated-sources/common-maven/remote-openapi/src")) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + + + // WHEN + /* Execute the mojo again */ + mojo.execute(); + + // THEN + /* Verify that the source directory has not been repopulated. If it has then we generated code again */ + assertTrue("src directory should have been regenerated", + folder.resolve("target/generated-sources/common-maven/remote-openapi/src").toFile().exists()); + + } + protected CodeGenMojo loadMojo(File temporaryFolder, String projectRoot) throws Exception { return loadMojo(temporaryFolder, projectRoot, "default"); } diff --git a/modules/openapi-generator-maven-plugin/src/test/resources/classpath/pom.xml b/modules/openapi-generator-maven-plugin/src/test/resources/classpath/pom.xml new file mode 100644 index 00000000000..6a841479f9d --- /dev/null +++ b/modules/openapi-generator-maven-plugin/src/test/resources/classpath/pom.xml @@ -0,0 +1,59 @@ + + + + 4.0.0 + common.maven + common-maven + jar + 1.0.0-SNAPSHOT + OpenAPI Generator Configuration Test + https://openapi-generator.tech/ + + common-maven + + + org.openapitools + openapi-generator-maven-plugin + + + petstore-on-classpath.yaml + java + + true + + + joda + + jersey2 + ${basedir}/target/generated-sources/common-maven/remote-openapi + remote.org.openapitools.client.api + remote.org.openapitools.client.model + remote.org.openapitools.client + + + + executionId + generate-sources + + generate + + + + + + + \ No newline at end of file diff --git a/modules/openapi-generator-maven-plugin/src/test/resources/petstore-on-classpath.yaml b/modules/openapi-generator-maven-plugin/src/test/resources/petstore-on-classpath.yaml new file mode 100644 index 00000000000..f5e98eec38d --- /dev/null +++ b/modules/openapi-generator-maven-plugin/src/test/resources/petstore-on-classpath.yaml @@ -0,0 +1,736 @@ +openapi: 3.0.0 +servers: + - url: 'http://petstore.swagger.io/v2' +info: + description: >- + This is a sample server Petstore server. For this sample, you can use the api key + `special-key` to test the authorization filters. + version: 1.0.0 + title: OpenAPI Petstore + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: false + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: false + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'read:pets' + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + additionalMetadata: + description: Additional data to pass to server + type: string + file: + description: file to upload + type: string + format: binary + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 5 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + Set-Cookie: + description: >- + Cookie authentication key for use with the `api_key` + apiKey authentication. + schema: + type: string + example: AUTH_KEY=abcde12345; Path=/; HttpOnly + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when toekn expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation + security: + - api_key: [] + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing. + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found + security: + - api_key: [] +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + requestBodies: + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header + schemas: + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + xml: + name: Category + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string