Skip to content

Commit

Permalink
FI-1236 feat: add "full mocks" functionality
Browse files Browse the repository at this point in the history
tests: add tests for "full mocks" functionality
fix: pack compilation errors `is not under 'rootDir'`
  • Loading branch information
uid11 committed May 20, 2024
1 parent 154f699 commit eaffd6d
Show file tree
Hide file tree
Showing 67 changed files with 1,045 additions and 141 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ If `true`, page fires `touch` events when test interact with the page (instead o
filters tests (tasks) by their static options —
only those tests for which the function returned `true` get into the pack.

`fullMocks: FullMocksConfig | null`: functions that specify the "full mocks" functionality.

`liteReportFileName: string | null`: the name of the file under which, after running the tests,
the lite JSON report will be saved in the `autotests/reports` directory, for example, `lite-report.json`.
If `null`, the lite report will not be saved.
Expand Down
31 changes: 31 additions & 0 deletions autotests/configurator/fullMocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {readFile} from 'node:fs/promises';
import {join} from 'node:path';

import {BAD_REQUEST_STATUS_CODE, READ_FILE_OPTIONS} from 'e2ed/constants';
import {writeFile} from 'e2ed/utils';

import type {FullMocks} from 'autotests/configurator';
import type {FilePathFromRoot, FullMocksTestId, RequestKind, TestFullMocks} from 'e2ed/types';

const fullMocksStoragePath = join('autotests', 'fixtures', 'fullMocks');

const getTestFullMocksPath = (testId: FullMocksTestId): FilePathFromRoot =>
join(fullMocksStoragePath, `${testId}.json`) as FilePathFromRoot;

export const fullMocks: FullMocks = {
filterTests: ({options: {meta}}) => meta.testId === '18',
getRequestKind: (method, {pathname}) => pathname as RequestKind,
getResponseFromFullMocks: ({responseWithRequest}) =>
responseWithRequest ?? {statusCode: BAD_REQUEST_STATUS_CODE},
getResponseToWriteToFullMocks: (requestKind, responseWithRequest) => responseWithRequest,
readTestFullMocks: async (testId) => {
const testFullMocksJson = await readFile(getTestFullMocksPath(testId), READ_FILE_OPTIONS);

return JSON.parse(testFullMocksJson) as TestFullMocks;
},
writeTestFullMocks: async (testId, testFullMocks) => {
const testFullMocksJson = JSON.stringify(testFullMocks);

await writeFile(getTestFullMocksPath(testId), testFullMocksJson);
},
};
2 changes: 2 additions & 0 deletions autotests/configurator/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export {doAfterPack} from './doAfterPack';
export {doBeforePack} from './doBeforePack';
export {fullMocks} from './fullMocks';
export {mapBackendResponseErrorToLog} from './mapBackendResponseErrorToLog';
export {mapBackendResponseToLog} from './mapBackendResponseToLog';
export {mapLogPayloadInConsole} from './mapLogPayloadInConsole';
Expand All @@ -10,6 +11,7 @@ export type {
DoAfterPack,
DoBeforePack,
FilterTestsIntoPack,
FullMocks,
GetFullPackConfig,
GetLogContext,
GetMainTestRunParams,
Expand Down
1 change: 1 addition & 0 deletions autotests/configurator/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type {
DoAfterPack,
DoBeforePack,
FilterTestsIntoPack,
FullMocks,
GetFullPackConfig,
GetLogContext,
GetMainTestRunParams,
Expand Down
1 change: 1 addition & 0 deletions autotests/configurator/types/packSpecific.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type GetLogContext = PackSpecificTypes['GetLogContext'];
export type GetMainTestRunParams = PackSpecificTypes['GetMainTestRunParams'];
export type GetTestRunHash = PackSpecificTypes['GetTestRunHash'];
export type FilterTestsIntoPack = PackSpecificTypes['FilterTestsIntoPack'];
export type FullMocks = PackSpecificTypes['FullMocks'];
export type IsTestSkipped = PackSpecificTypes['IsTestSkipped'];
export type LiteReport = PackSpecificTypes['LiteReport'];
export type MapBackendResponseErrorToLog = PackSpecificTypes['MapBackendResponseErrorToLog'];
Expand Down
1 change: 1 addition & 0 deletions autotests/entities/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export {createDevice} from './device';
export {addProduct} from './product';
export {createUser} from './user';
export {addUser, getUsers} from './worker';
21 changes: 21 additions & 0 deletions autotests/entities/product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {createClientFunction} from 'e2ed';

import type {ApiProduct, Product} from 'autotests/types';

/**
* Adds product.
*/
export const addProduct = createClientFunction(
(product: Product) =>
fetch(`https://reqres.in/api/product/${product.id}?size=${product.size}`, {
body: JSON.stringify({
cookies: [],
input: product.input,
model: product.model,
version: product.version,
}),
headers: {'Content-Type': 'application/json; charset=UTF-8'},
method: 'POST',
}).then((res) => res.json() as Promise<ApiProduct>),
{name: 'addProduct', timeout: 2_000},
);
4 changes: 3 additions & 1 deletion autotests/packs/allTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {isLocalRun} from 'e2ed/configurator';
import {
doAfterPack,
doBeforePack,
fullMocks,
mapBackendResponseErrorToLog,
mapBackendResponseToLog,
mapLogPayloadInConsole,
Expand Down Expand Up @@ -56,6 +57,7 @@ export const pack: Pack = {
enableMobileDeviceMode: false,
enableTouchEventEmulation: false,
filterTestsIntoPack,
fullMocks,
liteReportFileName: 'lite-report.json',
logFileName: 'pack-logs.log',
mapBackendResponseErrorToLog,
Expand All @@ -77,7 +79,7 @@ export const pack: Pack = {
skipTests,
takeFullPageScreenshotOnError: false,
takeViewportScreenshotOnError: true,
testFileGlobs: ['./autotests/tests/**/*.ts', '!**/*.skip.ts'],
testFileGlobs: ['./autotests/tests/**/fullMocks.ts', '!**/*.skip.ts'],
testIdleTimeout: 20_000,
testTimeout: 60_000,
viewportHeight: 1080,
Expand Down
6 changes: 3 additions & 3 deletions autotests/pageObjects/MobilePage.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Page} from 'e2ed';

import type {MobileDevice} from 'autotests/types';
import type {MobileDeviceModel} from 'autotests/types';

type PageParams<CustomPageParams> = CustomPageParams & Readonly<{mobileDevice?: MobileDevice}>;
type PageParams<CustomPageParams> = CustomPageParams & Readonly<{mobileDevice?: MobileDeviceModel}>;

/**
* Abstract mobile page.
Expand All @@ -11,5 +11,5 @@ export abstract class MobilePage<CustomPageParams> extends Page<PageParams<Custo
/**
* Type of mobile device.
*/
abstract readonly mobileDevice: MobileDevice;
abstract readonly mobileDevice: MobileDeviceModel;
}
8 changes: 6 additions & 2 deletions autotests/routes/apiRoutes/CreateDevice.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import {ApiRoute} from 'autotests/routes';

import type {ApiCreateDeviceRequest, ApiCreateDeviceResponse, MobileDevice} from 'autotests/types';
import type {
ApiCreateDeviceRequest,
ApiCreateDeviceResponse,
MobileDeviceModel,
} from 'autotests/types';

type Params = Readonly<{model: MobileDevice}>;
type Params = Readonly<{model: MobileDeviceModel}>;

/**
* Test API route for creating a device.
Expand Down
6 changes: 3 additions & 3 deletions autotests/routes/apiRoutes/CreateProduct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {URL} from 'node:url';
import {ApiRoute} from 'autotests/routes';
import {assertValueIsTrue} from 'e2ed/utils';

import type {ApiCreateProductRequest, ApiCreateProductResponse} from 'autotests/types';
import type {ApiCreateProductRequest, ApiCreateProductResponse, ProductId} from 'autotests/types';
import type {Url} from 'e2ed/types';

type Params = Readonly<{id: number; size: number}>;
type Params = Readonly<{id: ProductId; size: number}>;

const pathStart = '/api/product/';

Expand All @@ -27,7 +27,7 @@ export class CreateProduct extends ApiRoute<
{urlObject},
);

const id = Number(urlObject.pathname.slice(pathStart.length));
const id = Number(urlObject.pathname.slice(pathStart.length)) as ProductId;
const size = Number(urlObject.searchParams.get('size'));

assertValueIsTrue(Number.isInteger(id), 'url has correct id', {id, size, urlObject});
Expand Down
60 changes: 60 additions & 0 deletions autotests/tests/e2edReportExample/fullMocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {test} from 'autotests';
import {addProduct} from 'autotests/entities';
import {E2edReportExample} from 'autotests/pageObjects/pages';
import {CreateProduct as CreateProductRoute} from 'autotests/routes/apiRoutes';
import {expect} from 'e2ed';
import {mockApiRoute, navigateToPage, unmockApiRoute} from 'e2ed/actions';

import type {DeviceId, Product, ProductId} from 'autotests/types';
import type {Url} from 'e2ed/types';

test('full mocks works correctly', {meta: {testId: '18'}}, async () => {
await navigateToPage(E2edReportExample);

await mockApiRoute(CreateProductRoute, (routeParams, {method, query, requestBody, url}) => {
const responseBody = {
id: routeParams.id,
method,
output: String(requestBody.input),
payload: {id: String(routeParams.id) as DeviceId, ...requestBody},
query,
url,
};

return {responseBody};
});

const productId = Number('135865') as ProductId;
const product: Product = {
id: productId,
input: 17,
model: 'samsung',
size: '13',
version: '12',
};

const mockedProduct = await addProduct(product);

const fetchUrl = `https://reqres.in/api/product/${productId}?size=${product.size}` as Url;

await expect(mockedProduct, 'mocked API returns correct result').eql({
id: productId,
method: 'POST',
output: String(product.input),
payload: {
cookies: [],
id: String(productId) as DeviceId,
input: product.input,
model: product.model,
version: product.version,
},
query: {size: product.size},
url: fetchUrl,
});

await unmockApiRoute(CreateProductRoute);

const newMockedProduct = await addProduct(product);

await expect('createdAt' in newMockedProduct, 'API mock on CreateProductRoute was umocked').ok();
});
13 changes: 10 additions & 3 deletions autotests/tests/internalTypeTests/mockApiRoute.skip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ import {Main} from 'autotests/routes/pageRoutes';
import {mockApiRoute, unmockApiRoute} from 'e2ed/actions';
import {CREATED_STATUS_CODE, OK_STATUS_CODE} from 'e2ed/constants';

import type {ApiCreateDeviceRequest, ApiCreateDeviceResponse, DeviceId} from 'autotests/types';
import type {
ApiCreateDeviceRequest,
ApiCreateDeviceResponse,
DeviceId,
ProductId,
} from 'autotests/types';

const apiMockFunction = (
routeParams: object,
{method, query, requestBody, url}: ApiCreateDeviceRequest,
): Partial<ApiCreateDeviceResponse> => {
const {input} = requestBody;
const productId = 12;

const responseBody = {
id: 12,
id: productId as ProductId,
method,
output: String(input),
payload: {id: '12' as DeviceId, ...requestBody},
Expand Down Expand Up @@ -49,9 +55,10 @@ void mockApiRoute(
{method, requestBody, query, url}, // eslint-disable-next-line @typescript-eslint/require-await
) => {
const {input} = requestBody;
const productId = 7;

const responseBody = {
id: 7,
id: productId as ProductId,
method,
output: `${input}${routeParams.id}`,
payload: {id: '7' as DeviceId, ...requestBody},
Expand Down
2 changes: 1 addition & 1 deletion autotests/tests/internalTypeTests/pages.skip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {Main, Search, Services} from 'autotests/pageObjects/pages';
import {navigateToPage} from 'e2ed/actions';

/**
* PageParams = Readonly<{mobileDevice?: MobileDevice, query?: string}>
* PageParams = Readonly<{mobileDevice?: MobileDeviceModel, query?: string}>
*/

// ok
Expand Down
4 changes: 2 additions & 2 deletions autotests/tests/internalTypeTests/request.skip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {Main} from 'autotests/routes/pageRoutes';
import {getRandomId} from 'e2ed/generators';
import {request} from 'e2ed/utils';

import type {ApiDevice, ApiDeviceParams, ApiUserParams, MobileDevice} from 'autotests/types';
import type {ApiDevice, ApiDeviceParams, ApiUserParams, MobileDeviceModel} from 'autotests/types';

declare const apiUserParams: ApiUserParams;
declare const model: MobileDevice;
declare const model: MobileDeviceModel;
declare const apiDeviceParams: ApiDeviceParams;

// @ts-expect-error: request require API route as first argument
Expand Down
42 changes: 23 additions & 19 deletions autotests/tests/mockApiRoute.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {test} from 'autotests';
import {addProduct} from 'autotests/entities';
import {CreateProduct as CreateProductRoute} from 'autotests/routes/apiRoutes';
import {createClientFunction, expect} from 'e2ed';
import {expect} from 'e2ed';
import {mockApiRoute, unmockApiRoute} from 'e2ed/actions';

import type {ApiCreateDeviceResponse, DeviceId} from 'autotests/types';
import type {DeviceId, Product, ProductId} from 'autotests/types';
import type {Url} from 'e2ed/types';

test(
Expand All @@ -23,42 +24,45 @@ test(
return {responseBody};
});

const getMockedProduct = createClientFunction(
() =>
fetch('https://reqres.in/api/product/135865?size=13', {
body: JSON.stringify({cookies: [], input: 17, model: 'samsung', version: '12'}),
headers: {'Content-Type': 'application/json; charset=UTF-8'},
method: 'POST',
}).then((res) => res.json() as Promise<ApiCreateDeviceResponse['responseBody']>),
{name: 'getMockedProduct', timeout: 2_000},
);
const productId = 135865;
const product: Product = {
id: productId as ProductId,
input: 17,
model: 'samsung',
size: '13',
version: '12',
};

const mockedProduct = await getMockedProduct();
const mockedProduct = await addProduct(product);

const fetchUrl = 'https://reqres.in/api/product/135865?size=13' as Url;
const fetchUrl = `https://reqres.in/api/product/${productId}?size=${product.size}` as Url;

const productRouteParams = CreateProductRoute.getParamsFromUrl(fetchUrl);

const productRouteFromUrl = new CreateProductRoute(productRouteParams);

await expect(productRouteFromUrl.routeParams.id, 'route has correct params').eql(
productId as ProductId,
);

await expect(mockedProduct, 'mocked API returns correct result').eql({
id: productRouteFromUrl.routeParams.id,
method: productRouteFromUrl.getMethod(),
output: '17',
output: String(product.input),
payload: {
cookies: [],
id: String(productRouteFromUrl.routeParams.id) as DeviceId,
input: 17,
model: 'samsung',
version: '12',
input: product.input,
model: product.model,
version: product.version,
},
query: {size: '13'},
query: {size: product.size},
url: fetchUrl,
});

await unmockApiRoute(CreateProductRoute);

const newMockedProduct = (await getMockedProduct().catch(() => undefined)) ?? {createdAt: ''};
const newMockedProduct = await addProduct(product);

await expect(
'createdAt' in newMockedProduct,
Expand Down
14 changes: 3 additions & 11 deletions autotests/types/api/CreateDevice.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import type {ApiDevice, ApiDeviceParams} from 'autotests/types';
import type {Method, Query, Request, Response, Url} from 'e2ed/types';
import type {ApiDeviceParams, ApiProduct} from 'autotests/types';
import type {Query, Request, Response} from 'e2ed/types';

type RequestBody = ApiDeviceParams;

type ResponseBody = Readonly<{
id: number;
method: Method;
output: string;
payload: ApiDevice;
query: Query;
url: Url;
}>;

type ResponseBody = ApiProduct;
/**
* API request for create device endpoint.
*/
Expand Down
Loading

0 comments on commit eaffd6d

Please sign in to comment.