Skip to content

Commit

Permalink
refactor phase 1
Browse files Browse the repository at this point in the history
  • Loading branch information
Romarionijim committed Apr 11, 2024
1 parent 17f911d commit 0c05014
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 76 deletions.
184 changes: 118 additions & 66 deletions infra/api/apiClient/ApiClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { el } from "@faker-js/faker";
import { APIRequestContext, APIResponse, expect } from "@playwright/test";
import { APIRequestContext, APIResponse } from '@playwright/test';

export enum RequestMethods {
GET = 'GET',
Expand All @@ -20,31 +19,41 @@ 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 },
requestData?: { [key: string]: T },
authoriaztionRequired?: boolean,
isMultiPart?: boolean,
multipartObject?: { [key: string]: any },
multiPartData?: { [key: string]: any },
paginateRequest?: boolean,
pagePagination?: boolean,
limitOffsetPagination?: boolean,
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 @@ -71,106 +80,149 @@ 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?.multipartObject! })
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?.multipartObject! })
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?.multipartObject! })
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
}


private async paginateBy<T>(paginationType: PaginationType, options?: ApiOptionalParams<T>): Promise<{ [key: string]: any }> {
let existingQueryParams = { ...options?.queryParams }
let newParams = {}
switch (paginationType) {
case PaginationType.PAGE_PAGINATION:
newParams = { ...existingQueryParams, 'page': options?.pageNumber }
break;
case PaginationType.OFFSET_PAGINATION:
newParams = { ...existingQueryParams, 'limit': options?.limit, 'offset': options?.offset }
break;
}
return newParams;
}

/**
* @description function that supports pagination via page pagination or by limit and offset pagination
* @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
*/
protected async paginateRequest<T>(method: RequestMethods, url: string, options?: ApiOptionalParams<T>) {
let existingQueryParams = { ...options?.queryParams }
private async handleResponseObject(responses: APIResponse[], responseObj: any) {
if (Array.isArray(responseObj)) {
responses.push(...responseObj)
} else {
responses.push(responseObj);
}
}

public async paginateRequest<T>(method: RequestMethods, url: string, pagintionType: PaginationType, options: ApiOptionalParams<T>): Promise<APIResponse[] | undefined> {
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++
const queryParams = await this.paginateBy(pagintionType, options)
if (options.pagePagination && options.pageNumber !== undefined) {
queryParams['page'] = options.pageNumber++
}
response = await this.makeRequest(method, url, options);
let responseObj = await response?.json();
if (!responseObj || responseObj.length === 0) {
break;
}
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?.responseKey) {
let responseKey = responseObj[options.responseKey]
if (responseKey.length === 0) {
break;
}
if (options.responseDataKey !== undefined) {
let responseKey = responseObject[options.responseDataKey]
if (responseKey.length === 0) {
break;
await this.handleResponseObject(responseKey, responses);

} else {
await this.handleResponseObject(responseObj, responses);
}
switch (options.paginationType) {
case PaginationType.PAGE_PAGINATION:
if (options.pageNumber !== undefined) {
options.pageNumber++;
}
responses.push(...responseKey)
} else {
if (Array.isArray(responseObject)) {
responses.push(...responseObject)
} else {
responses.push(responseObject)
break;
case PaginationType.OFFSET_PAGINATION:
if (options.offset !== undefined && options.limit !== undefined) {
options.offset += options.limit;
}
}
if (options.offset !== undefined && options.limit !== undefined) {
options.offset += options.limit
}
break;
}
}
return responses

return responses;
} catch (error) {
throw new Error(`caught an error in the paginate request function: ${error}`)
throw new Error(`something went wrong in one of the paginateRequest function conditions - please refer to paginateRequest function `)
}
}

/**
* @description http request that abstracts the logic behind the scenes
*/
private async httpRequest<T>(method: RequestMethods, url: string, options?: ApiOptionalParams<T>) {
let response = await this.makeRequest(method, url, { queryParams: options?.queryParams, requestData: options?.requestData, authoriaztionRequired: options?.authoriaztionRequired, isMultiPart: options?.isMultiPart, multipartObject: options?.multipartObject })
private incrementPaginationParams<T>(options: ApiOptionalParams<T>, paginationType: PaginationType) {
switch (paginationType) {
case PaginationType.PAGE_PAGINATION:
if (options.pageNumber !== undefined) {
options.pageNumber++;
}
break;
case PaginationType.OFFSET_PAGINATION:
if (options.offset !== undefined && options.limit !== undefined) {
options.offset += options.limit;
}
break;
}
}

public async paginateHttpRequest<T>(method: RequestMethods, url: string, options?: { paginationType?: PaginationType } & ApiOptionalParams<T>) {
if (options?.paginateRequest) {
let responses = await this.paginateRequest(method, url, options.paginationType!, options);
if (responses === undefined) {
throw new Error('the response object is udnefined in the paginateHttpRequest ');
}
return responses;
} else {
throw new Error('pagination options may not have been provided in the makePaginatedHttpRequest');
}
}

private async makeHttpRequest<T>(method: RequestMethods, 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, { requestData: options?.queryParams, paginateRequest: options?.paginateRequest, limit: options?.limit, offset: options?.offset, pagePagination: options?.pagePagination, limitOffsetPagination: options?.limitOffsetPagination, responseDataKey: options?.responseDataKey })
return response
let response = await this.makeHttpRequest(RequestMethods.GET, url, options)
return response;
}

public async post<T>(url: string, data: { [key: string]: T }, options?: ApiOptionalParams<T>) {
let response = await this.httpRequest(RequestMethods.POST, url, { isMultiPart: options?.isMultiPart, requestData: data, multipartObject: options?.multipartObject, paginateRequest: options?.paginateRequest, limit: options?.limit, offset: options?.offset, pagePagination: options?.pagePagination, limitOffsetPagination: options?.limitOffsetPagination, authoriaztionRequired: options?.authoriaztionRequired })
return response
public async post<T>(url: string, options?: ApiOptionalParams<T>) {
let response = await this.makeHttpRequest(RequestMethods.POST, url, options)
return response;
}

public async put<T>(url: string, data: { [key: string]: T }, options?: ApiOptionalParams<T>) {
let response = await this.httpRequest(RequestMethods.PUT, url, { requestData: data, paginateRequest: options?.paginateRequest, limit: options?.limit, offset: options?.offset, pagePagination: options?.pagePagination, limitOffsetPagination: options?.limitOffsetPagination, authoriaztionRequired: options?.authoriaztionRequired });
return response
public async put<T>(url: string, options?: ApiOptionalParams<T>) {
let response = await this.makeHttpRequest(RequestMethods.PUT, url, options)
return response;
}

public async patch<T>(url: string, data?: { [key: string]: T }, options?: ApiOptionalParams<T>) {
let response = await this.httpRequest(RequestMethods.PATCH, url, { requestData: data, paginateRequest: options?.paginateRequest, limit: options?.limit, offset: options?.offset, pagePagination: options?.pagePagination, limitOffsetPagination: options?.limitOffsetPagination, authoriaztionRequired: options?.authoriaztionRequired });
public async patch<T>(url: string, options?: ApiOptionalParams<T>) {
let response = await this.makeHttpRequest(RequestMethods.PATCH, url, options)
return response;
}

public async delete<T>(url: string, options?: ApiOptionalParams<T>) {
let response = await this.httpRequest(RequestMethods.DELETE, url, { paginateRequest: options?.paginateRequest, limit: options?.limit, offset: options?.offset, pagePagination: options?.pagePagination, limitOffsetPagination: options?.limitOffsetPagination, authoriaztionRequired: options?.authoriaztionRequired });
let response = await this.makeHttpRequest(RequestMethods.DELETE, url, options)
return response;
}
}
10 changes: 5 additions & 5 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 } from "../../apiClient/ApiClient";
import { ApiClient, PaginationType, RequestMethods } from "../../apiClient/ApiClient";
import Randomizer from "../../helpers/faker/Randomizer";
import { ApplicationUrl } from "../../helpers/urls/ApplicationUrl";

Expand Down Expand Up @@ -49,7 +49,7 @@ export class Users extends ApiClient {
gender: 'female',
status: 'active',
}
response = await this.post(this.usersEnpoint, femaleData, { authoriaztionRequired: true })
response = await this.post(this.usersEnpoint, { requestData: femaleData, authoriaztionRequired: true })
}
} else {
for (let i = 0; i < differrence; i++) {
Expand All @@ -60,7 +60,7 @@ export class Users extends ApiClient {
gender: 'male',
status: 'active',
}
response = await this.post(this.usersEnpoint, maleData, { authoriaztionRequired: true })
response = await this.post(this.usersEnpoint, { requestData: maleData, authoriaztionRequired: true })
}
}

Expand Down Expand Up @@ -96,7 +96,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.paginateHttpRequest(RequestMethods.GET, this.usersEnpoint, { paginateRequest: true, paginationType: PaginationType.PAGE_PAGINATION, pagePagination: true, pageNumber: page })
return response;
}

Expand Down Expand Up @@ -129,7 +129,7 @@ export class Users extends ApiClient {
if (emailExtension && emailExtension !== 'co.il') {
let newEmail = await this.replaceEmailExtension(user.email, '.co.il')
let newEmailProperty = { email: newEmail }
response = await this.patch(`${this.usersEnpoint}/${user.id}`, newEmailProperty, { authoriaztionRequired: true })
response = await this.patch(`${this.usersEnpoint}/${user.id}`, { requestData: newEmailProperty, authoriaztionRequired: true })
}
}
return response
Expand Down
2 changes: 1 addition & 1 deletion infra/api/entities/petStore/PetStoreCrudActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ 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, RequestMethods, StatusCode } from "../../apiClient/ApiClient";
import { ApplicationUrl } from "../../helpers/urls/ApplicationUrl";

export class PokemonApi extends ApiClient {
Expand All @@ -14,8 +14,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.paginateHttpRequest(RequestMethods.GET, this.POKEMON_ENDPOINT, { paginateRequest: true, limit: limit, offset: offset, paginationType: PaginationType.OFFSET_PAGINATION, responseKey: 'results' })
return responses;
}
}
2 changes: 1 addition & 1 deletion tests/api_tests/pokemon/PokemonApiTests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ 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 response = await pokemonApi.getAllPokemonRecourses(limit, offset)
let responseLength = response.length
expect(responseLength).toBe(1302)
})
Expand Down

0 comments on commit 0c05014

Please sign in to comment.