Skip to content

Commit

Permalink
refactored version
Browse files Browse the repository at this point in the history
  • Loading branch information
Romarionijim committed Apr 12, 2024
1 parent 22536d4 commit ef1c49c
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 90 deletions.
155 changes: 83 additions & 72 deletions infra/api/apiClient/ApiClient.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { el } from "@faker-js/faker";
import { APIRequestContext, APIResponse, expect } from "@playwright/test";
import { ur } from '@faker-js/faker';
import { APIRequestContext, APIResponse } from '@playwright/test';

export enum RequestMethods {
export enum RequestMethod {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
Expand All @@ -20,6 +20,13 @@ export enum StatusCode {
SERVER_ERROR = 500,
}

export enum PaginationType {
PAGE_PAGINATION = 'page',
OFFSET_PAGINATION = 'offset',
CURSOR_PAGINATION = 'cursor',
}


export interface ApiOptionalParams<T> {
responseDataKey?: string,
queryParams?: { [key: string]: any },
Expand All @@ -33,18 +40,21 @@ export interface ApiOptionalParams<T> {
pageNumber?: number,
limit?: number,
offset?: number,
cursor?: boolean,
cursorKey?: string,
paginationType?: PaginationType,
responseKey?: string,
}


export class ApiClient {
constructor(public apiRequestContext: APIRequestContext) {
this.apiRequestContext = apiRequestContext
constructor(public request: APIRequestContext) {
this.request = request;
}

/**
* @description resuable code to add the authorization header if an authorization is requiired to make the request
* @param headers
*/
* @description resuable code to add the authorization header if an authorization is requiired to make the request
* @param headers
*/
private async addAuthorizationHeader(headers: { [key: string]: string }) {
headers['Authorization'] = `Bearer ${process.env.API_TOKEN}`
}
Expand All @@ -56,7 +66,7 @@ export class ApiClient {
* @param options
* @returns
*/
private async makeRequest<T>(method: RequestMethods, url: string, options?: ApiOptionalParams<T>): Promise<APIResponse | undefined> {
private async makeRequest<T>(method: RequestMethod, url: string, options?: ApiOptionalParams<T>): Promise<APIResponse | undefined> {
let response: APIResponse | undefined
let headers: Record<string, string> = {
'Accept': '*/*'
Expand All @@ -71,106 +81,107 @@ export class ApiClient {
}
switch (method.valueOf()) {
case 'GET':
response = await this.apiRequestContext.get(url, { headers, params: options?.queryParams })
response = await this.request.get(url, { headers, params: options?.queryParams })
break;
case 'POST':
response = await this.apiRequestContext.post(url, { headers, data: options?.requestData, multipart: options?.multiPartData! })
response = await this.request.post(url, { headers, data: options?.requestData, multipart: options?.multiPartData! })
break;
case 'PUT':
response = await this.apiRequestContext.put(url, { headers, data: options?.requestData, multipart: options?.multiPartData! })
response = await this.request.put(url, { headers, data: options?.requestData, multipart: options?.multiPartData! })
break;
case 'PATCH':
response = await this.apiRequestContext.patch(url, { headers, data: options?.requestData, multipart: options?.multiPartData! })
response = await this.request.patch(url, { headers, data: options?.requestData, multipart: options?.multiPartData! })
break;
case 'DELETE':
response = await this.apiRequestContext.delete(url)
response = await this.request.delete(url)
break;
}
return response
}

/**
* @description function that supports pagination via page pagination or by limit and offset pagination
*/
protected async paginateRequest<T>(method: RequestMethods, url: string, options?: ApiOptionalParams<T>) {
let existingQueryParams = { ...options?.queryParams }
let response: APIResponse | undefined
let responses: APIResponse[] = []
try {
while (true) {
if (options?.pagePagination && options?.pageNumber !== undefined) {
existingQueryParams['page'] = options.pageNumber
response = await this.makeRequest(method, url, { queryParams: existingQueryParams, requestData: options.requestData, authoriaztionRequired: options.authoriaztionRequired })
let responseObject = await response?.json()
if (!responseObject || responseObject.length === 0) {
break
}
responses.push(...responseObject)
options.pageNumber++
}
if (options?.limitOffsetPagination) {
existingQueryParams['limit'] = options.limit
existingQueryParams['offset'] = options.offset
response = await this.makeRequest(method, url, { queryParams: existingQueryParams, requestData: options.requestData, authoriaztionRequired: options.authoriaztionRequired })
let responseObject = await response?.json()
if (!responseObject || responseObject.length === 0) {
break
}
if (options.responseDataKey !== undefined) {
let responseKey = responseObject[options.responseDataKey]
if (responseKey.length === 0) {
break;
}
responses.push(...responseKey)
} else {
if (Array.isArray(responseObject)) {
responses.push(...responseObject)
} else {
responses.push(responseObject)
}
}
if (options.offset !== undefined && options.limit !== undefined) {
options.offset += options.limit
}

private async paginateBy<T>(method: RequestMethod, url: string, paginationType: PaginationType, options?: ApiOptionalParams<T>) {
let response: APIResponse | undefined;
let responses: APIResponse[] = [];
let queryParams = options?.queryParams ? { ...options.queryParams } : {};
while (true) {
if (paginationType === PaginationType.PAGE_PAGINATION && options?.pageNumber !== undefined) {
queryParams = { ...queryParams, 'page': options.pageNumber };
} else if (paginationType === PaginationType.OFFSET_PAGINATION && options?.limit !== undefined && options.offset !== undefined) {
queryParams = { ...queryParams, 'limit': options.limit, 'offset': options.offset };
}
response = await this.makeRequest(method, url, { ...options, queryParams });
let responseObj = await response?.json();
if (!responseObj || responseObj.length === 0) {
break;
}
if (options?.responseKey) {
let responseKey = responseObj[options.responseKey];
if (responseKey.length === 0) {
break;
}
await this.handleResponseObject(responses, responseKey);
} else {
await this.handleResponseObject(responses, responseObj);
}
return responses
if (paginationType === PaginationType.PAGE_PAGINATION && options?.pageNumber !== undefined) {
options.pageNumber++;
} else if (paginationType === PaginationType.OFFSET_PAGINATION && options?.offset !== undefined && options.limit !== undefined) {
options.offset += options.limit;
}
}

return responses;
}
/**
* @description handle the response object by spreading it to an existing array if the response is already an array otherwise push directly
* to the array.
* @param responseObj
*/
private async handleResponseObject(responses: APIResponse[], responseObj: any) {
if (Array.isArray(responseObj)) {
responses.push(...responseObj)
} else {
responses.push(responseObj);
}
}

public async paginateRequest<T>(method: RequestMethod, url: string, pagintionType: PaginationType, options: ApiOptionalParams<T>) {
try {
let response = await this.paginateBy(method, url, pagintionType, options);
return response;
} catch (error) {
throw new Error(`caught an error in the paginate request function: ${error}`)
throw new Error(`an error occured in the paginate request function: ${error}`)
}
}

/**
* @description http request that abstracts the logic behind the scenes
*/
private async httpRequest<T>(method: RequestMethods, url: string, options?: ApiOptionalParams<T>) {
private async makeHttpRequest<T>(method: RequestMethod, url: string, options?: ApiOptionalParams<T>) {
let response = await this.makeRequest(method, url, options)
return response;
}

public async get<T>(url: string, options?: ApiOptionalParams<T>) {
let response = await this.httpRequest(RequestMethods.GET, url, options)
return response
let response = await this.makeHttpRequest(RequestMethod.GET, url, options)
return response;
}

public async post<T>(url: string, options?: ApiOptionalParams<T>) {
let response = await this.httpRequest(RequestMethods.POST, url, options)
return response
let response = await this.makeHttpRequest(RequestMethod.POST, url, options)
return response;
}

public async put<T>(url: string, options?: ApiOptionalParams<T>) {
let response = await this.httpRequest(RequestMethods.PUT, url, options);
return response
let response = await this.makeHttpRequest(RequestMethod.PUT, url, options)
return response;
}

public async patch<T>(url: string, options?: ApiOptionalParams<T>) {
let response = await this.httpRequest(RequestMethods.PATCH, url, options);
let response = await this.makeHttpRequest(RequestMethod.PATCH, url, options)
return response;
}

public async delete<T>(url: string, options?: ApiOptionalParams<T>) {
let response = await this.httpRequest(RequestMethods.DELETE, url, options);
let response = await this.makeHttpRequest(RequestMethod.DELETE, url, options)
return response;
}
}
4 changes: 2 additions & 2 deletions infra/api/entities/gorestapi/Users.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APIResponse } from "@playwright/test";
import { ApiClient, RequestMethods, StatusCode } from "../../apiClient/ApiClient";
import { ApiClient, PaginationType, RequestMethod } from "../../apiClient/ApiClient";
import Randomizer from "../../helpers/faker/Randomizer";
import { ApplicationUrl } from "../../helpers/urls/ApplicationUrl";
import { ApiEndpoints } from "../../endpoints/ApiEndpoints";
Expand Down Expand Up @@ -89,7 +89,7 @@ export class Users extends ApiClient {
* @returns
*/
public async getAllUsers(page: number) {
let response = await this.paginateRequest(RequestMethods.GET, this.usersEnpoint, { paginateRequest: true, pagePagination: true, pageNumber: page })
let response = await this.paginateRequest(RequestMethod.GET, this.usersEnpoint, PaginationType.PAGE_PAGINATION, { paginateRequest: true, pagePagination: true, pageNumber: page })
return response;
}

Expand Down
1 change: 0 additions & 1 deletion infra/api/entities/petStore/PetStoreCrudActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,4 @@ export class PetStoreCrudActions extends ApiClient {
let response = await this.delete(`${this.petStorePetEndpoint}/${petId}`)
return response;
}

}
6 changes: 3 additions & 3 deletions infra/api/entities/pokemon/PokemonApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APIRequestContext, APIResponse, expect } from "@playwright/test";
import { ApiClient, RequestMethods, StatusCode } from "../../apiClient/ApiClient";
import { ApiClient, PaginationType, RequestMethod, StatusCode } from "../../apiClient/ApiClient";
import { ApplicationUrl } from "../../helpers/urls/ApplicationUrl";
import { ApiEndpoints } from "../../endpoints/ApiEndpoints";

Expand All @@ -15,8 +15,8 @@ export class PokemonApi extends ApiClient {
/**
* @description get all pokemon recourses by using pagination - you can choose via page or limit and offset pagination mechanism
*/
public async getAllPokemonRecourses(limit: number, offset: number, options?: { pagePagination?: boolean, limitOffsetPagination?: boolean }) {
let responses = await this.paginateRequest(RequestMethods.GET, this.POKEMON_ENDPOINT, { limit: limit, offset: offset, limitOffsetPagination: options?.limitOffsetPagination, responseDataKey: 'results' })
public async getAllPokemonRecourses(limit: number, offset: number) {
let responses = await this.paginateRequest(RequestMethod.GET, this.POKEMON_ENDPOINT, PaginationType.OFFSET_PAGINATION, { limitOffsetPagination: true, limit: limit, offset: offset, responseKey: 'results' })
return responses;
}
}
9 changes: 0 additions & 9 deletions tests/api_tests/goRestApi/GoRestApiTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ test.describe('Api tests for GoRestApi endpoints', async () => {
users = new Users(request);
})


test.skip('get all users', { tag: ['@GO_REST_API'] }, async () => {
await test.step('get all users from all pages from users endpoint', async () => {
let res = await users.getAllUsers(pageNumber)
expect(res.every(res => res.status())).toBe(StatusCode.OK)
})
})


test('sanity check', { tag: ['@GO_REST_API'] }, async () => {
await test.step('get users endpoint - validate status, body type of obejct properties and default length of the response', async () => {
let response = await users.getUsers();
Expand Down
2 changes: 1 addition & 1 deletion tests/api_tests/petStore/PetStoreCrudTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test.describe.serial('CRUD API tests for the Pet Store API', async () => {
let response = await petStoreCrudActions.getPet(id)
let responseJson: Ipet = await response?.json()
expect(response?.status()).toBe(StatusCode.OK)
expect(responseJson.name).toBe('ZAP')
expect(responseJson.name).toBe('doggie')
})
})

Expand Down
4 changes: 2 additions & 2 deletions tests/api_tests/pokemon/PokemonApiTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ test.describe('Pokemon API CRUD tests', async () => {

test('get all pokemon resources', { tag: ['@POKEMON_API'] }, async () => {
await test.step('get all pokemon recourses via limit and offset pagination', async () => {
let response = await pokemonApi.getAllPokemonRecourses(limit, offset, { limitOffsetPagination: true })
let responseLength = response.length
let response = await pokemonApi.getAllPokemonRecourses(limit, offset)
let responseLength = response?.length
expect(responseLength).toBe(1302)
})
})
Expand Down

0 comments on commit ef1c49c

Please sign in to comment.