diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index 76869e066e..930de6f897 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -751,7 +751,8 @@ class EMConfig { @Important(3.2) @Url @Cfg("When in black-box mode for REST APIs, specify the URL of where the OpenAPI/Swagger schema can be downloaded from." + - " If the schema is on the local machine, you can use a URL starting with 'file://'") + " If the schema is on the local machine, you can use a URL starting with 'file://'." + + " If the given URL is neither starting with 'file' nor 'http', then it will be treated as a local file path.") var bbSwaggerUrl: String = "" @Important(3.5) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/OpenApiAccess.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/OpenApiAccess.kt index 24ef14e375..96152ce04a 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/OpenApiAccess.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/OpenApiAccess.kt @@ -62,16 +62,32 @@ object OpenApiAccess { } private fun readFromDisk(openApiUrl: String) : String { + + // file schema val fileScheme = "file:" - val path = if (openApiUrl.startsWith(fileScheme, true)) { - Paths.get(URI.create(openApiUrl)) - } else { - Paths.get(openApiUrl) + + // create paths + val path = try { + if (openApiUrl.startsWith(fileScheme, true)) { + Paths.get(URI.create(openApiUrl)) + } + else { + Paths.get(openApiUrl) + } + } + // Exception is thrown if the path is not valid + catch (e: Exception) { + // state the exception with the error message + throw SutProblemException("The file path provided for the OpenAPI Schema $openApiUrl" + + " ended up with the following error: " + e.message) } + + // If the path is valid but the file does not exist, an exception is thrown if (!Files.exists(path)) { - throw SutProblemException("Cannot find OpenAPI schema at file location: $openApiUrl") + throw SutProblemException("The provided OpenAPI file does not exist: $openApiUrl") } + // return the schema text return path.toFile().readText() } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/AbstractRestSampler.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/AbstractRestSampler.kt index 2039da7a60..dc09eaf671 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/AbstractRestSampler.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/AbstractRestSampler.kt @@ -251,7 +251,12 @@ abstract class AbstractRestSampler : HttpWsSampler() { swagger = OpenApiAccess.getOpenAPIFromURL(configuration.bbSwaggerUrl) if (swagger.paths == null) { - throw SutProblemException("There is no endpoint definition in the retrieved Swagger file") + throw SutProblemException("There is no endpoint definition in the retrieved OpenAPI file") + } + // Onur: to give the error message for invalid swagger + if (swagger.paths.size == 0){ + throw SutProblemException("The OpenAPI file ${configuration.bbSwaggerUrl} " + + "is either invalid or it does not define endpoints") } // ONUR: Add all paths to list of paths to ignore except endpointFocus diff --git a/core/src/test/kotlin/org/evomaster/core/problem/rest/OpenAPILocalURLIssueTest.kt b/core/src/test/kotlin/org/evomaster/core/problem/rest/OpenAPILocalURLIssueTest.kt new file mode 100644 index 0000000000..f35e378c67 --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/problem/rest/OpenAPILocalURLIssueTest.kt @@ -0,0 +1,299 @@ +package org.evomaster.core.problem.rest + +import org.evomaster.core.remote.SutProblemException + +import io.swagger.v3.oas.models.OpenAPI + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +import java.util.* + +/* +Testing the local URL issue with OpenAPI, 4 test cases: +1. A local file which exists and the provided URL is valid. +2. A local file does not exist but the provided URL is valid. +3. A local file exists but the provided URL is not valid. +4. A local file does not exist and the provided URL is not valid. +5. A local file which exists but the relative file path is provided. +6. Two different exceptions in Windows and others (e.g., "URI Path Component is empty" is tested here). +7. The swagger is an existing valid json file, but it is not a valid swagger. +8: The swagger is an invalid json file. + */ +class OpenAPILocalURLIssueTest { + + // companion object to set up tests. + companion object { + + // execution path, it can be different from one machine to another + private var executionPath :String = System.getProperty("user.dir") + + // swagger object + private lateinit var swagger: OpenAPI + + // swagger test directory to find test files + private lateinit var swaggerTestDirectory: String + + // host operating system + private lateinit var hostOs: String + + @JvmStatic + @BeforeAll + // This is to deal with differences in Windows and Linux paths + fun setSwaggerDirectoryBasedOnOS() { + + // get the name of the current operating system + hostOs = System.getProperty("os.name").lowercase(Locale.getDefault()) + + // if the operating system is Windows, then replace \ with / + if (hostOs.contains("win")) { + executionPath = executionPath.replace('\\', '/') + } + + // swagger files for testing + swaggerTestDirectory = "$executionPath/src/test/resources/swagger/urlissue" + } + } + + /* + Test Case 1: A local file which exists and the provided URL is valid + Check that the swagger is created with a valid URL and an existing file + */ + @Test + fun testExistingFileValidURL() { + + // get the current directory, in Mac or Linux, it starts with file:// + // but in Windows, it has to have just one file:/ + val urlToTest = if (hostOs.contains("win")) { + "file:/$swaggerTestDirectory/openapi_pet.json" + } + else { + "file://$swaggerTestDirectory/openapi_pet.json" + } + + // create swagger from URL + swagger = OpenApiAccess.getOpenAPIFromURL(urlToTest) + + // a valid swagger is created with 13 endpoints + Assertions.assertTrue(swagger.paths.size == 13) + } + + /* + Test Case 2: A local file schema does not exist but the provided URL is valid + Check that an exception is thrown which states that the provided swagger file does not exist + */ + @Test + fun testNonExistingFileValidURL() { + + // The Windows file URL starts with file:/ + val urlToTest = if (hostOs.contains("win")) { + "file:/$swaggerTestDirectory/openapi_pet_non_existing.json" + } + // file URL in other operating systems + else { + "file://$swaggerTestDirectory/openapi_pet_non_existing.json" + } + + // since the file does not exist, a valid swagger cannot be created + // but an SutException should be thrown + val exception = Assertions.assertThrows( + SutProblemException::class.java + ) { + // create swagger from URL + swagger = OpenApiAccess.getOpenAPIFromURL(urlToTest) + } + + // the message in the SutException should be "The provided swagger file does not exist: $urlToTest + // check that the message is correct", it is the same for both Windows and other operating systems + Assertions.assertTrue(exception.message!!.contains("The provided OpenAPI file does " + + "not exist: $urlToTest")) + } + + /* + Test Case 3: A local file which exists but the provided URL is invalid + Check that an exception is thrown when the file exists but the URL is not valid, for Windows file:// is + not valid, for others file:/ is not valid + */ + @Test + fun testExistingFileInvalidURL() { + + // The Windows file URL starts with file://, which is not valid + val urlToTest = if (hostOs.contains("win")) { + "file://$swaggerTestDirectory/openapi_pet.json" + } + // URL starts with file:/ for other operating systems. + else { + "file:/$swaggerTestDirectory/openapi_pet.json" + } + + // since the file URL is invalid, a valid swagger cannot be created, + // so an SutException should be thrown + val exception = Assertions.assertThrows( + SutProblemException::class.java + ) { + swagger = OpenApiAccess.getOpenAPIFromURL(urlToTest) + } + + // In windows, the file cannot be located, in others the error is URL related + if (hostOs.contains("win")) { + Assertions.assertTrue(exception.message!!.contains("The provided OpenAPI file " + + "does not exist: $urlToTest")) + } + else { + Assertions.assertTrue(exception.message!!.contains("The file path provided for the " + + "OpenAPI Schema $urlToTest ended up with the following error: ")) + } + + } + + /* Test Case 4: A local file does not exist and the provided URL is not valid + Check that an exception is thrown for non-existent file in Windows, and an SUTException + is thrown with the error message for others. + */ + @Test + fun testNonExistingFileInvalidURL() { + + // File path in Windows and others + val urlToTest = if (hostOs.contains("win")) { + "file://$swaggerTestDirectory/openapi_pet_non_existent.json" + } + else { + "file:/$swaggerTestDirectory/openapi_pet_non_existent.json" + } + + // since the file does not exist and URL is invalid, a valid swagger cannot be created + // but an SutException should be thrown + val exception = Assertions.assertThrows( + SutProblemException::class.java + ) { + swagger = OpenApiAccess.getOpenAPIFromURL(urlToTest) + } + + // In windows, the file cannot be located, in others the error is URL related + if (hostOs.contains("win")) { + Assertions.assertTrue(exception.message!!.contains("The provided OpenAPI file " + + "does not exist: $urlToTest")) + } + else { + Assertions.assertTrue(exception.message!!.contains("The file path provided for the OpenAPI " + + "Schema $urlToTest ended up with the following error: ")) + } + } + + + @Test + /* Test Case 5: A local file which exists but the relative file path is provided. + In that case, an exception stating the file does not exist is thrown in Windows + SutException is thrown in other operating systems. + */ + fun testRelativeFilePathExistingFile() { + + // file path in Windows and others + val urlToTest = if (hostOs.contains("win")) { + "file:/./src/test/resources/swagger/openapi_pet.json" + } + else { + "file://./src/test/resources/swagger/openapi_pet.json" + } + + // create swagger + val exception = Assertions.assertThrows( + SutProblemException::class.java + ) { + swagger = OpenApiAccess.getOpenAPIFromURL(urlToTest) + } + + // Assert the thrown exception + if (hostOs.contains("win")) { + Assertions.assertTrue(exception.message!!.contains("The provided OpenAPI file " + + "does not exist: $urlToTest")) + } + else { + Assertions.assertTrue(exception.message!!.contains("The file path provided for the " + + "OpenAPI Schema $urlToTest ended up with the following error: ")) + } + } + + /* + Test case 6: Test for two different exceptions in Windows and others + For the URL file://openapi_pet.json, Windows throws URI path component is empty, + others throw URI has an authority component + */ + @Test + fun testFileNameOnlyNonExistingFile() { + + // same URL for both Windows and others, but different exceptions are expected + val urlToTest = "file://openapi_pet.json" + + // create swagger + val exception = Assertions.assertThrows( + SutProblemException::class.java + ) { + swagger = OpenApiAccess.getOpenAPIFromURL(urlToTest) + } + + // Check the thrown exception for windows and others + if (hostOs.contains("win")) { + Assertions.assertTrue(exception.message!!.contains("The file path provided for the " + + "OpenAPI Schema $urlToTest ended up with the following error: " + + "URI path component is empty")) + } + else { + Assertions.assertTrue(exception.message!!.contains("The file path provided for the " + + "OpenAPI Schema $urlToTest ended up with the following error: " + + "URI has an authority component" + ) + ) + } + } + + /* + Test case 7: If the swagger is an existing valid json file, but it is not + a valid swagger, a swagger object is created with 0 endpoints + */ + @Test + fun testInvalidSwagger() { + + // file path in Windows and others + val urlToTest = if (hostOs.contains("win")) { + "file:/$swaggerTestDirectory/invalid_swagger.json" + } + else { + "file://$swaggerTestDirectory/invalid_swagger.json" + } + + // create swagger + swagger = OpenApiAccess.getOpenAPIFromURL(urlToTest) + + //An empty swagger should be created + Assertions.assertTrue(swagger.paths.size == 0) + } + + /* + Test case 8: If the swagger is an invalid json file, an exception stating that the swagger + could not be parsed should be thrown + */ + @Test + fun testInvalidJSON() { + + // file path in Windows and others + val urlToTest = if (hostOs.contains("win")) { + "file:/$swaggerTestDirectory/invalid_json.json" + } + else { + "file://$swaggerTestDirectory/invalid_json.json" + } + + // create swagger + val exception = Assertions.assertThrows( + SutProblemException::class.java + ) { + // create swagger + swagger = OpenApiAccess.getOpenAPIFromURL(urlToTest) + } + + // Failed to parse OpenApi schema + Assertions.assertTrue( exception.message!!.contains("Failed to parse OpenApi schema")) + } +} \ No newline at end of file diff --git a/core/src/test/resources/swagger/urlissue/invalid_json.json b/core/src/test/resources/swagger/urlissue/invalid_json.json new file mode 100644 index 0000000000..bef92ef763 --- /dev/null +++ b/core/src/test/resources/swagger/urlissue/invalid_json.json @@ -0,0 +1 @@ +AAAAAAAAAAA \ No newline at end of file diff --git a/core/src/test/resources/swagger/urlissue/invalid_swagger.json b/core/src/test/resources/swagger/urlissue/invalid_swagger.json new file mode 100644 index 0000000000..2b3bbc37b9 --- /dev/null +++ b/core/src/test/resources/swagger/urlissue/invalid_swagger.json @@ -0,0 +1,3 @@ +{ + "key" : "value" +} \ No newline at end of file diff --git a/core/src/test/resources/swagger/urlissue/openapi_pet.json b/core/src/test/resources/swagger/urlissue/openapi_pet.json new file mode 100644 index 0000000000..1b546da4a4 --- /dev/null +++ b/core/src/test/resources/swagger/urlissue/openapi_pet.json @@ -0,0 +1,1225 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Swagger Petstore - OpenAPI 3.0", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.17" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "servers": [ + { + "url": "/api/v3" + } + ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "requestBody": { + "description": "Update an existent pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "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" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "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" + ] + } + ] + } + }, + "/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": false, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + } + } + ], + "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": [ + "write:pets", + "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": false, + "explode": true, + "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": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/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": [] + }, + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "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" + } + }, + { + "name": "name", + "in": "query", + "description": "Name of pet that needs to be updated", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Status of pet that needs to be updated", + "schema": { + "type": "string" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "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" + } + }, + { + "name": "additionalMetadata", + "in": "query", + "description": "Additional Metadata", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/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": "Place a new order in the store", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "405": { + "description": "Invalid input" + } + } + } + }, + "/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 generate exceptions.", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of order that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "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": "integer", + "format": "int64" + } + } + ], + "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", + "requestBody": { + "description": "Created user object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "Creates list of users with given input array", + "operationId": "createUsersWithListInput", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "successful operation" + } + } + } + }, + "/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": false, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token 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", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/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": "Update user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that needs to be updated", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation" + } + } + }, + "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" + } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "example": "approved", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "order" + } + }, + "Customer": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 100000 + }, + "username": { + "type": "string", + "example": "fehguy" + }, + "address": { + "type": "array", + "xml": { + "name": "addresses", + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Address" + } + } + }, + "xml": { + "name": "customer" + } + }, + "Address": { + "type": "object", + "properties": { + "street": { + "type": "string", + "example": "437 Lytton" + }, + "city": { + "type": "string", + "example": "Palo Alto" + }, + "state": { + "type": "string", + "example": "CA" + }, + "zip": { + "type": "string", + "example": "94301" + } + }, + "xml": { + "name": "address" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": "12345" + }, + "phone": { + "type": "string", + "example": "12345" + }, + "userStatus": { + "type": "integer", + "description": "User Status", + "format": "int32", + "example": 1 + } + }, + "xml": { + "name": "user" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "tag" + } + }, + "Pet": { + "required": [ + "name", + "photoUrls" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "xml": { + "name": "##default" + } + } + }, + "requestBodies": { + "Pet": { + "description": "Pet object that needs to be added to the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "UserArray": { + "description": "List of user object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} \ No newline at end of file diff --git a/docs/options.md b/docs/options.md index 968ab9007f..cbb55af598 100644 --- a/docs/options.md +++ b/docs/options.md @@ -40,7 +40,7 @@ There are 3 types of options: |`outputFormat`| __Enum__. Specify in which format the tests should be outputted. If left on `DEFAULT`, then the value specified in the _EvoMaster Driver_ will be used. But a different value must be chosen if doing Black-Box testing. *Valid values*: `DEFAULT, JAVA_JUNIT_5, JAVA_JUNIT_4, KOTLIN_JUNIT_4, KOTLIN_JUNIT_5, JS_JEST, CSHARP_XUNIT`. *Default value*: `DEFAULT`.| |`testTimeout`| __Int__. Enforce timeout (in seconds) in the generated tests. This feature might not be supported in all frameworks. If 0 or negative, the timeout is not applied. *Default value*: `60`.| |`blackBox`| __Boolean__. Use EvoMaster in black-box mode. This does not require an EvoMaster Driver up and running. However, you will need to provide further option to specify how to connect to the SUT. *Default value*: `false`.| -|`bbSwaggerUrl`| __String__. When in black-box mode for REST APIs, specify the URL of where the OpenAPI/Swagger schema can be downloaded from. If the schema is on the local machine, you can use a URL starting with 'file://'. *Constraints*: `URL`. *Default value*: `""`.| +|`bbSwaggerUrl`| __String__. When in black-box mode for REST APIs, specify the URL of where the OpenAPI/Swagger schema can be downloaded from. If the schema is on the local machine, you can use a URL starting with 'file://'. If the given URL is neither starting with 'file' nor 'http', then it will be treated as a local file path. *Constraints*: `URL`. *Default value*: `""`.| |`bbTargetUrl`| __String__. When in black-box mode, specify the URL of where the SUT can be reached, e.g., http://localhost:8080 . In REST, if this is missing, the URL will be inferred from OpenAPI/Swagger schema. In GraphQL, this must point to the entry point of the API, e.g., http://localhost:8080/graphql . *Constraints*: `URL`. *Default value*: `""`.| |`ratePerMinute`| __Int__. Rate limiter, of how many actions to do per minute. For example, when making HTTP calls towards an external service, might want to limit the number of calls to avoid bombarding such service (which could end up becoming equivalent to a DoS attack). A value of zero or negative means that no limiter is applied. This is needed only for black-box testing of remote services. *Default value*: `0`.| |`header0`| __String__. In black-box testing, we still need to deal with authentication of the HTTP requests. With this parameter it is possible to specify a HTTP header that is going to be added to most requests. This should be provided in the form _name:value_. If more than 1 header is needed, use as well the other options _header1_ and _header2_. *Constraints*: `regex (.+:.+)\|(^$)`. *Default value*: `""`.|