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