From 59c657eb055f30030eb219fb82f04e3f9f023d1c Mon Sep 17 00:00:00 2001 From: hmalik144 Date: Mon, 22 Jul 2024 13:40:23 +0100 Subject: [PATCH] readme.md added --- .gitignore | 8 +- LICENSE.md | 19 ++++ pom.xml | 37 +++---- readme.md | 98 +++++++++++++++++++ src/main/kotlin/org/example/api/ApiUtils.kt | 7 ++ src/main/kotlin/org/example/api/BookerApi.kt | 1 - .../org/example/api/RestfulBookerApi.kt | 6 +- .../org/example/model/BookingResponse.kt | 2 +- src/main/resources/Log4j2.xml | 2 +- .../{NetworkTests.kt => BaseNetworkTests.kt} | 12 ++- src/test/kotlin/org/example/Tests.kt | 75 ++++++-------- .../kotlin/org/example/utils/Constants.kt | 4 + .../utils/{FileReader.kt => TestHelper.kt} | 6 +- src/test/resources/test2.json | 11 +-- src/test/resources/test3.json | 8 +- 15 files changed, 211 insertions(+), 85 deletions(-) create mode 100644 LICENSE.md create mode 100644 readme.md create mode 100644 src/main/kotlin/org/example/api/ApiUtils.kt rename src/test/kotlin/org/example/{NetworkTests.kt => BaseNetworkTests.kt} (58%) create mode 100644 src/test/kotlin/org/example/utils/Constants.kt rename src/test/kotlin/org/example/utils/{FileReader.kt => TestHelper.kt} (86%) diff --git a/.gitignore b/.gitignore index 5ff6309..dc99a93 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,10 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +### dot env ### +.env + +### report results ### +app-info.html \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..193ebf9 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/pom.xml b/pom.xml index e71109e..0accf11 100644 --- a/pom.xml +++ b/pom.xml @@ -85,13 +85,7 @@ org.junit.jupiter - junit-jupiter-engine - 5.10.0 - test - - - org.junit.jupiter - junit-jupiter-api + junit-jupiter 5.10.0 test @@ -100,12 +94,6 @@ kotlin-stdlib 1.9.0 - - - org.junit.jupiter - junit-jupiter-params - 5.0.0 - junit junit @@ -129,11 +117,11 @@ logging-interceptor 4.12.0 - - org.example - api-testing-automation-framework - 1.0-SNAPSHOT - + + + + + org.jetbrains.kotlinx kotlinx-coroutines-core @@ -157,9 +145,16 @@ test - net.sf.jasperreports - jasperreports - 6.20.0 + io.github.cdimascio + dotenv-kotlin + 6.4.1 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M7 + maven-plugin diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..7f6d135 --- /dev/null +++ b/readme.md @@ -0,0 +1,98 @@ +# Automation API Testing Framework + +This repository contains an Automation API Testing Framework built with Kotlin and Maven. The framework is designed to provide a robust, scalable, and maintainable structure for automated testing of RESTful APIs. + +## Table of Contents +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Project Structure](#project-structure) +- [Configuration](#configuration) +- [Prerequisites for Testing](#prerequisites-for-testing) +- [Running Tests](#running-tests) +- [Reporting](#reporting) +- [Contributing](#contributing) +- [License](#license) + +## Prerequisites +Before you begin, ensure you have met the following requirements: +- You have installed [JDK 17](https://www.oracle.com/java/technologies/downloads/#java17) or later. +- You have installed [Maven](https://maven.apache.org/install.html). +- You have installed [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). +- IDE capable of running Kotlin. eg. Intellij, Eclipse (requires kotlin plugin), VS Studio (requires kotlin plugin) [although it is possible to run from the command line] + +## Installation +1. Clone the repository: + ```sh + git clone https://github.com/your-username/automation-api-testing-framework.git + ``` +2. Navigate to the project directory: + ```sh + cd automation-api-testing-framework + ``` +3. Install the dependencies: + ```sh + mvn -DskipTests=true package + ``` + +## Project Structure +The framework follows a standard Maven project structure: +``` +automation-api-testing-framework +├── src +│ ├── main +│ │ └── kotlin +│ │ └── org +│ │ └── example +│ │ ├── api +│ │ ├── model +│ │ ├── storage +│ ├── test +│ │ └── kotlin +│ │ └── org +│ │ └── example +│ │ ├── utils +│ │ ├── ... +├── pom.xml +└── README.md +``` + +- `api`: Api which is to be tested. +- `model`: data classes of the responses and requests for api. +- `storage`: caching class for storing api data. +- `utils`: Helper functions and utilities. + +## Configuration +Configuration files are located in the `src/main/kotlin/com/yourpackage/config` directory. Modify the configuration files as per your testing environment. + +## Prerequisites for Testing +### Create a .env File +In the root directory of your project, create a file named .env. This file will hold all your environment-specific variables. For example: +```env +API_USERNAME={username-here} +API_PASSWORD={password-here} +``` + +## Running Tests +To run the tests, use the following Maven command: +```sh +mvn test +``` + +## Reporting +Test output reports are generated in the `app-info.html` directory by default. + +## Contributing +Contributions are welcome! Please follow these steps to contribute: +1. Fork the repository. +2. Create a new branch (`git checkout -b feature-branch`). +3. Make your changes. +4. Commit your changes (`git commit -m 'Add some feature'`). +5. Push to the branch (`git push origin feature-branch`). +6. Open a pull request. + +## License +This project is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for details. + +--- + +Thank you for using the Automation API Testing Framework! If you have any questions, feel free to open an issue or contact the project maintainers. \ No newline at end of file diff --git a/src/main/kotlin/org/example/api/ApiUtils.kt b/src/main/kotlin/org/example/api/ApiUtils.kt new file mode 100644 index 0000000..1341240 --- /dev/null +++ b/src/main/kotlin/org/example/api/ApiUtils.kt @@ -0,0 +1,7 @@ +package org.example.api + +import java.util.* + +fun createBasicAuthTokenForHeader(username: String, password: String) = + StringBuilder("Basic ").append(Base64.getEncoder().encodeToString("$username:$password".toByteArray())) + .toString() \ No newline at end of file diff --git a/src/main/kotlin/org/example/api/BookerApi.kt b/src/main/kotlin/org/example/api/BookerApi.kt index 9a9964f..9f03c8c 100644 --- a/src/main/kotlin/org/example/api/BookerApi.kt +++ b/src/main/kotlin/org/example/api/BookerApi.kt @@ -36,7 +36,6 @@ class BookerApi { .setLenient() .create() - private fun buildOkHttpClient(timeoutSeconds: Long = 30L): OkHttpClient { val builder = OkHttpClient.Builder() diff --git a/src/main/kotlin/org/example/api/RestfulBookerApi.kt b/src/main/kotlin/org/example/api/RestfulBookerApi.kt index 71a28dc..32d6c46 100644 --- a/src/main/kotlin/org/example/api/RestfulBookerApi.kt +++ b/src/main/kotlin/org/example/api/RestfulBookerApi.kt @@ -33,20 +33,20 @@ interface RestfulBookerApi { suspend fun updateBooking( @Path("id") id: String, @Body booking: BookingRequest, - @Header("Authorization") token: String + @Header("Authorization") basicHeaderToken: String ): Response @Headers("Content-Type:application/json", "Accept: application/json") @PATCH("booking/{id}") suspend fun partialUpdateBooking( @Path("id") id: String, - @Header("Authorization") token: String, + @Header("Authorization") basicHeaderToken: String, @Body update: UpdateBookingRequest ): Response @DELETE("booking/{id}") suspend fun deleteBooking( @Path("id") id: String, - @Header("Authorization") token: String + @Header("Authorization") basicHeaderToken: String ): Response } \ No newline at end of file diff --git a/src/main/kotlin/org/example/model/BookingResponse.kt b/src/main/kotlin/org/example/model/BookingResponse.kt index e1b171a..d646ec6 100644 --- a/src/main/kotlin/org/example/model/BookingResponse.kt +++ b/src/main/kotlin/org/example/model/BookingResponse.kt @@ -7,5 +7,5 @@ data class BookingResponse( var totalprice: Int, var depositpaid: Boolean, var bookingdates: Bookingdates, - var additionalneeds: String + var additionalneeds: String? ) \ No newline at end of file diff --git a/src/main/resources/Log4j2.xml b/src/main/resources/Log4j2.xml index c4c5a64..76ae612 100644 --- a/src/main/resources/Log4j2.xml +++ b/src/main/resources/Log4j2.xml @@ -1,7 +1,7 @@ - Classpath + ./ responseUnwrap( @@ -22,4 +28,8 @@ abstract class NetworkTests { } } + fun getStoredVariable(key: String) = env.get(key) + + fun logMessage(logMessage: String) = logger.info(logMessage) + fun logMessage(logMessage: MessageFormatMessage) = logger.info(logMessage) } \ No newline at end of file diff --git a/src/test/kotlin/org/example/Tests.kt b/src/test/kotlin/org/example/Tests.kt index fc74b64..57e224b 100644 --- a/src/test/kotlin/org/example/Tests.kt +++ b/src/test/kotlin/org/example/Tests.kt @@ -2,26 +2,25 @@ package org.example import org.example.api.BookerApi import org.example.api.RestfulBookerApi -import org.example.model.AuthRequest import org.example.model.BookingRequest -import org.example.model.Bookingdates import org.example.model.UpdateBookingRequest -import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.message.MessageFormatMessage import org.assertj.core.api.AssertionsForClassTypes.assertThat +import org.example.api.createBasicAuthTokenForHeader import org.junit.jupiter.api.* import org.example.storage.OrdersDatabase -import org.example.utils.FileReader +import org.example.utils.TestHelper +import org.example.utils.PASSWORD_KEY +import org.example.utils.USERNAME_KEY import java.util.* @TestMethodOrder(MethodOrderer.OrderAnnotation::class) -class Tests : NetworkTests() { +class Tests : BaseNetworkTests() { companion object { private lateinit var bookerApi: RestfulBookerApi - private val fileReader = FileReader() - private val logger = LogManager.getLogger(Tests::javaClass) + private val testHelper = TestHelper() private val storage = OrdersDatabase() @@ -33,8 +32,8 @@ class Tests : NetworkTests() { internal fun beforeAll() { bookerApi = BookerApi().invoke() - bookingRequestTestOne = fileReader.readJsonFileFromResources("test1", BookingRequest::class.java) - bookingRequestTestTwo = fileReader.readJsonFileFromResources("test2", BookingRequest::class.java) + bookingRequestTestOne = testHelper.readJsonFileFromResources("test1", BookingRequest::class.java) + bookingRequestTestTwo = testHelper.readJsonFileFromResources("test2", BookingRequest::class.java) } @AfterAll @@ -56,17 +55,7 @@ class Tests : NetworkTests() { /* * Given */ - val bookingRequestThree = BookingRequest( - firstname = "Mark", - lastname = "Wahlberg", - totalprice = 750, - depositpaid = true, - bookingdates = Bookingdates( - checkin = "2025-01-01", - checkout = "2025-01-10" - ), - additionalneeds = "Breakfast" - ) + val bookingRequestThree = testHelper.readJsonFileFromResources("test3", BookingRequest::class.java) /* * When @@ -85,17 +74,17 @@ class Tests : NetworkTests() { .isGreaterThanOrEqualTo(3) val bookingIds = bookingIdsResponse.map { it.bookingid }.joinToString() - logger.info("Available booking IDs: $bookingIds") + logMessage("Available booking IDs: $bookingIds") // Add the booking details and idea for later bookingResponses.forEach { response -> storage.insertBooking(response.bookingid, response.booking) - logger.trace( + logMessage( MessageFormatMessage( "Booking with ID: {0} has been added: {1}", response.bookingid, - response.booking + testHelper.toJsonString(response.booking) ) ) } @@ -130,21 +119,11 @@ class Tests : NetworkTests() { /* * When */ - val auth = responseUnwrap { - bookerApi.createAuthToken( - AuthRequest( - UUID.randomUUID().toString(), - UUID.randomUUID().toString() - ) - ) - } - val tokenBuilder = - StringBuilder("Basic ").append(Base64.getEncoder().encodeToString("admin:password123".toByteArray())) - .toString() + val tokenBuilder = buildBasicAuthToken() val updateTestOneResponse = responseUnwrap { bookerApi.partialUpdateBooking( id = orderIdTestOne.toString(), - token = tokenBuilder, + basicHeaderToken = tokenBuilder, update = UpdateBookingRequest( totalprice = 1000 ) @@ -155,7 +134,7 @@ class Tests : NetworkTests() { val updateTestTwoResponse = responseUnwrap { bookerApi.partialUpdateBooking( id = orderIdTestTwo.toString(), - token = tokenBuilder, + basicHeaderToken = tokenBuilder, update = UpdateBookingRequest( totalprice = 1500 ) @@ -163,23 +142,22 @@ class Tests : NetworkTests() { }.also { storage.updateCompleteOrder(orderIdTestTwo, it) } - val updateResponseList = listOf(updateTestOneResponse, updateTestTwoResponse) /* * Then */ - logger.info( + logMessage( MessageFormatMessage( "Booking with ID: {0} has been updated to the following: {1}", orderIdTestOne, - updateTestOneResponse + testHelper.toJsonString(updateTestOneResponse) ) ) - logger.info( + logMessage( MessageFormatMessage( "Booking with ID: {0} has been updated to the following: {1}", orderIdTestTwo, - updateTestTwoResponse + testHelper.toJsonString(updateTestTwoResponse) ) ) } @@ -205,11 +183,11 @@ class Tests : NetworkTests() { /* * Then */ - logger.info( + logMessage( MessageFormatMessage( "Booking with ID: {0} has been delete and given the following response: {1}", idOfAny, - deleteResponse + testHelper.toJsonString(deleteResponse) ) ) } @@ -220,6 +198,7 @@ class Tests : NetworkTests() { /* * Given */ + println("The log4j library will create the report in the root of the folder") /* * When @@ -229,4 +208,14 @@ class Tests : NetworkTests() { * Then */ } + + /** + * build a basic header token needed for using end points like partial update + */ + private fun buildBasicAuthToken(): String { + val username = getStoredVariable(USERNAME_KEY) + val password = getStoredVariable(PASSWORD_KEY) + + return createBasicAuthTokenForHeader(username, password) + } } \ No newline at end of file diff --git a/src/test/kotlin/org/example/utils/Constants.kt b/src/test/kotlin/org/example/utils/Constants.kt new file mode 100644 index 0000000..fbd69e0 --- /dev/null +++ b/src/test/kotlin/org/example/utils/Constants.kt @@ -0,0 +1,4 @@ +package org.example.utils + +const val USERNAME_KEY = "API_USERNAME" +const val PASSWORD_KEY = "API_PASSWORD" \ No newline at end of file diff --git a/src/test/kotlin/org/example/utils/FileReader.kt b/src/test/kotlin/org/example/utils/TestHelper.kt similarity index 86% rename from src/test/kotlin/org/example/utils/FileReader.kt rename to src/test/kotlin/org/example/utils/TestHelper.kt index d43e5db..52fa06f 100644 --- a/src/test/kotlin/org/example/utils/FileReader.kt +++ b/src/test/kotlin/org/example/utils/TestHelper.kt @@ -2,10 +2,8 @@ package org.example.utils import com.google.gson.Gson import java.io.BufferedReader -import java.lang.reflect.ParameterizedType -import kotlin.reflect.KClass -class FileReader { +class TestHelper { private val gson by lazy { Gson() } @@ -18,4 +16,6 @@ class FileReader { val data = iStream.bufferedReader().use(BufferedReader::readText) return gson.fromJson(data, clazz) } + + fun toJsonString(any: Any) = gson.toJson(any) } \ No newline at end of file diff --git a/src/test/resources/test2.json b/src/test/resources/test2.json index 8f86452..d6e6d12 100644 --- a/src/test/resources/test2.json +++ b/src/test/resources/test2.json @@ -1,11 +1,10 @@ { - "firstname" : "Jim", - "lastname" : "Brown", - "totalprice" : 500, - "depositpaid" : true, + "firstname" : "Andrea", + "lastname" : "Sims", + "totalprice" : 1000, + "depositpaid" : false, "bookingdates" : { "checkin" : "2025-01-01", "checkout" : "2025-01-10" - }, - "additionalneeds" : "Lunch" + } } \ No newline at end of file diff --git a/src/test/resources/test3.json b/src/test/resources/test3.json index 8f86452..4f7c34d 100644 --- a/src/test/resources/test3.json +++ b/src/test/resources/test3.json @@ -1,11 +1,11 @@ { - "firstname" : "Jim", - "lastname" : "Brown", - "totalprice" : 500, + "firstname" : "Mark", + "lastname" : "Wahlberg", + "totalprice" : 1500, "depositpaid" : true, "bookingdates" : { "checkin" : "2025-01-01", "checkout" : "2025-01-10" }, - "additionalneeds" : "Lunch" + "additionalneeds" : "Lunch|Dinner" } \ No newline at end of file