readme.md added

This commit is contained in:
2024-07-22 13:40:23 +01:00
parent 97773c7b01
commit 59c657eb05
15 changed files with 211 additions and 85 deletions

6
.gitignore vendored
View File

@@ -36,3 +36,9 @@ build/
### Mac OS ###
.DS_Store
### dot env ###
.env
### report results ###
app-info.html

19
LICENSE.md Normal file
View File

@@ -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.

37
pom.xml
View File

@@ -85,13 +85,7 @@
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
@@ -100,12 +94,6 @@
<artifactId>kotlin-stdlib</artifactId>
<version>1.9.0</version>
</dependency>
<!-- jUnit -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -129,11 +117,11 @@
<artifactId>logging-interceptor</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>api-testing-automation-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.example</groupId>-->
<!-- <artifactId>api-testing-automation-framework</artifactId>-->
<!-- <version>1.0-SNAPSHOT</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
@@ -157,9 +145,16 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.20.0</version>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-kotlin</artifactId>
<version>6.4.1</version>
</dependency>
<!-- https://maven.apache.org/surefire/maven-surefire-plugin/dependency-info.html -->
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
<type>maven-plugin</type>
</dependency>
</dependencies>

98
readme.md Normal file
View File

@@ -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.

View File

@@ -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()

View File

@@ -36,7 +36,6 @@ class BookerApi {
.setLenient()
.create()
private fun buildOkHttpClient(timeoutSeconds: Long = 30L): OkHttpClient {
val builder = OkHttpClient.Builder()

View File

@@ -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<BookingResponse>
@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<BookingResponse>
@DELETE("booking/{id}")
suspend fun deleteBooking(
@Path("id") id: String,
@Header("Authorization") token: String
@Header("Authorization") basicHeaderToken: String
): Response<Any>
}

View File

@@ -7,5 +7,5 @@ data class BookingResponse(
var totalprice: Int,
var depositpaid: Boolean,
var bookingdates: Bookingdates,
var additionalneeds: String
var additionalneeds: String?
)

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Properties>
<Property name="basePath">Classpath</Property>
<Property name="basePath">./</Property>
</Properties>
<Appenders>
<RollingFile name="fileLogger" fileName="${basePath}/app-info.html"

View File

@@ -1,10 +1,16 @@
package org.example
import io.github.cdimascio.dotenv.dotenv
import kotlinx.coroutines.runBlocking
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.message.MessageFormatMessage
import retrofit2.Response
import java.io.IOException
abstract class NetworkTests {
abstract class BaseNetworkTests {
private val logger = LogManager.getLogger(Tests::javaClass)
private val env by lazy { dotenv() }
// Call retrofit API and unwrap response or throw exception
fun <T : Any> 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)
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,4 @@
package org.example.utils
const val USERNAME_KEY = "API_USERNAME"
const val PASSWORD_KEY = "API_PASSWORD"

View File

@@ -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)
}

View File

@@ -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"
}
}

View File

@@ -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"
}