Skip to content

Commit

Permalink
fix: send body in delete requests
Browse files Browse the repository at this point in the history
  • Loading branch information
ErnoW committed Oct 6, 2022
1 parent 4af4eb8 commit 77eb29b
Show file tree
Hide file tree
Showing 8 changed files with 761 additions and 27 deletions.
6 changes: 6 additions & 0 deletions .changeset/wicked-badgers-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@moralisweb3/api-utils': patch
'@moralisweb3/core': patch
---

Sent body in delete requests. This fixes issues with Moralis.Streams.deleteAddress
12 changes: 9 additions & 3 deletions packages/apiUtils/src/resolvers/EndpointResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,16 @@ export class EndpointResolver<ApiParams, Params, ApiResult, AdaptedResult, JSONR
const apiParams = this.endpoint.parseParams(params);

const searchParams = this.paramsReader.getSearchParams(apiParams);
const bodyParams = this.paramsReader.getBodyParams(apiParams);

const result = await this.requestController.delete<ApiResult>(url, searchParams, {
headers: this.createHeaders(),
});
const result = await this.requestController.delete<ApiResult, Record<string, string>>(
url,
searchParams,
bodyParams,
{
headers: this.createHeaders(),
},
);

return new ApiResultAdapter(result, this.endpoint.apiToResult, this.endpoint.resultToJson, params);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,18 @@ export class RequestController {
});
}

public async delete<Response>(
public async delete<Response, Body extends Record<string, unknown>>(
url: string,
searchParams?: Record<string, unknown>,
body?: Body,
options?: RequestOptions,
abortSignal?: AbortController['signal'],
): Promise<Response> {
return this.request<unknown, Response>({
url,
params: searchParams,
method: 'DELETE',
data: body,
headers: options?.headers,
signal: abortSignal,
});
Expand Down
73 changes: 52 additions & 21 deletions packages/integration/mockRequests/streamApi/deleteAddress.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
import isEqual from 'lodash/isEqual';
import isNil from 'lodash/isNil';
import omitBy from 'lodash/omitBy';
import { rest } from 'msw';
import { STREAM_API_ROOT, MOCK_API_KEY } from '../config';

const DeleteAddressResponse: Record<string, { data: any; status: number }> = {
VALID_RESPONSE: {
data: {
streamId: 'VALID_RESPONSE',
const createResponse = (streamId: string, address: string) => ({
streamId,
address,
});

const createErrorResponse = (message: string) => ({
message,
});

// message: 'Validation Failed',
// details: { 'requestBody.address': { message: "'address' is required" } }

const scenarios = [
{
condition: {
id: 'VALID_RESPONSE',
address: '0x992eccc191d6f74e8be187ed6b6ac196b08314f7',
},
status: 200,
responseStatus: 200,
response: createResponse('VALID_RESPONSE', '0x992eccc191d6f74e8be187ed6b6ac196b08314f7'),
},
INVALID_ADDRESS: {
data: {
message: 'Invalid Address: some-address',
{
condition: {
id: 'INVALID_ADDRESS',
address: 'some-address',
},
status: 400,
responseStatus: 400,
response: createErrorResponse('Invalid Address: some-address'),
},
STREAM_NOT_FOUND: {
data: {
message: 'Stream not found',
{
condition: {
id: 'STREAM_NOT_FOUND',
address: '0x992eccc191d6f74e8be187ed6b6ac196b08314f7',
},
status: 404,
responseStatus: 404,
response: createErrorResponse('Stream not found'),
},
ADDRESS_NOT_FOUND: {
data: {
message: 'Address not found',
{
condition: {
id: 'ADDRESS_NOT_FOUND',
address: '0x295522b61890c3672d12efbff4358a6411ce996f',
},
status: 404,
responseStatus: 404,
response: createErrorResponse('Address not found'),
},
};
];

export const mockDeleteAddressEvm = rest.delete(`${STREAM_API_ROOT}/streams/evm/:id/address`, (req, res, ctx) => {
const apiKey = req.headers.get('x-api-key');
const id = req.params.id as string;
const { address } = req.body as Record<string, any>;

if (apiKey !== MOCK_API_KEY) {
return res(
Expand All @@ -42,9 +65,17 @@ export const mockDeleteAddressEvm = rest.delete(`${STREAM_API_ROOT}/streams/evm/
);
}

if (DeleteAddressResponse[id]) {
return res(ctx.status(DeleteAddressResponse[id].status), ctx.json(DeleteAddressResponse[id].data));
const params = omitBy({ id, address }, isNil);

if (apiKey !== MOCK_API_KEY) {
return res(ctx.status(401));
}

const scenario = scenarios.find(({ condition }) => isEqual(condition, params));

if (scenario) {
return res(ctx.status(scenario.responseStatus), ctx.json(scenario.response));
}

throw new Error('addAddressEvm: Not supported scenario');
throw new Error(`mockDeleteAddressEvm failed, no scenarios with: ${JSON.stringify(params)}`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import { MoralisCore, RequestController } from '@moralisweb3/core';
import { setupServer } from 'msw/node';
import { rest } from 'msw';

const deleteJsonMock = rest.delete(`http://example.com/deleteJson`, (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
iAm: 'Batman',
}),
);
});

const deleteTextMock = rest.delete(`http://example.com/deleteText`, (req, res, ctx) => {
return res(ctx.status(200), ctx.text('I am Batman'));
});

const delete400ErrorMock = rest.delete(`http://example.com/delete400Error`, (req, res, ctx) => {
return res(
ctx.status(400),
ctx.json({
message: 'I am not Batman',
}),
);
});

const delete404ErrorMock = rest.delete(`http://example.com/delete404Error`, (req, res, ctx) => {
return res(
ctx.status(404),
ctx.json({
message: 'I am not Batman',
}),
);
});

const delete500ErrorMock = rest.delete(`http://example.com/delete500Error`, (req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({
message: 'I am not Batman',
}),
);
});

const delete503ErrorMock = rest.delete(`http://example.com/delete503Error`, (req, res, ctx) => {
return res(
ctx.status(503),
ctx.json({
message: 'I am not Batman',
}),
);
});

const delete400ErrorMultiMessageMock = rest.delete(`http://example.com/delete400MultiMessageError`, (req, res, ctx) => {
return res(
ctx.status(400),
ctx.json({
message: ['I am not Batman', 'I am not superman'],
}),
);
});

const delete400ErrorEmptyJsonMock = rest.delete(`http://example.com/delete400ErrorEmptyJson`, (req, res, ctx) => {
return res(ctx.status(400), ctx.json({}));
});

const delete400ErrorEmptyMock = rest.delete(`http://example.com/delete400ErrorEmpty`, (req, res, ctx) => {
return res(ctx.status(400));
});

const deleteWithSearchParams = rest.delete(`http://example.com/deleteWithSearchParams`, (req, res, ctx) => {
const params = req.url.searchParams;
if (params.get('name') !== 'Batman' || params.get('age') !== '30') {
throw new Error(`Search Params are not being correctly sent. Got params: ${JSON.stringify(params)}`);
}
return res(ctx.status(200), ctx.json({ success: true }));
});

const deleteWithBodyParams = rest.delete(`http://example.com/deleteWithBodyParams`, (req, res, ctx) => {
const body = req.body as Record<string, unknown>;
if (body.name !== 'Batman' || body.age !== '30') {
throw new Error(`Body Params are not being correctly sent. Got body: ${JSON.stringify(body)}`);
}
return res(ctx.status(200), ctx.json({ success: true }));
});

const deleteWithSearchAndBodyParams = rest.delete(
`http://example.com/deleteWithSearchAndBodyParams`,
(req, res, ctx) => {
const body = req.body as Record<string, unknown>;
const params = req.url.searchParams;

if (params.get('name') !== 'Batman' || body.age !== '30') {
throw new Error(
`Params are not being correctly sent. Got body: ${JSON.stringify(body)}, Got params: ${JSON.stringify(params)}`,
);
}
return res(ctx.status(200), ctx.json({ success: true }));
},
);

const handlers = [
deleteJsonMock,
deleteTextMock,
delete400ErrorMock,
delete404ErrorMock,
delete500ErrorMock,
delete503ErrorMock,
delete400ErrorMultiMessageMock,
delete400ErrorEmptyJsonMock,
delete400ErrorEmptyMock,
deleteWithSearchParams,
deleteWithBodyParams,
deleteWithSearchAndBodyParams,
];

const mockServer = setupServer(...handlers);

describe('RequestControllerDelete', () => {
let requestController: RequestController;

beforeAll(() => {
const core = MoralisCore.create();
requestController = RequestController.create(core);

mockServer.listen({
onUnhandledRequest: 'warn',
});
});

afterAll(() => {
mockServer.close();
});

beforeEach(() => {});

afterEach(async () => {
/**
* Prevent issue with Jest and MSW when running multiple tests,
* where tests are finished before all requests are resolved
* https://github.com/mswjs/msw/issues/474
*/
await new Promise((resolve) => setTimeout(resolve.bind(null), 0));
});

it('should delete a valid Json response', async () => {
const result = await requestController.delete('http://example.com/deleteJson');

expect(result).toStrictEqual({ iAm: 'Batman' });
});

it('should delete a valid text response', async () => {
const result = await requestController.delete('http://example.com/deleteText');

expect(result).toStrictEqual('I am Batman');
});

it('should throw an error on 400 response', async () => {
expect(requestController.delete('http://example.com/delete400Error')).rejects.toThrowError(
'[C0006] Request failed, Bad Request(400): I am not Batman',
);
});

it('should throw an error on 404 response', async () => {
expect(requestController.delete('http://example.com/delete404Error')).rejects.toThrowError(
'[C0006] Request failed, Not Found(404): I am not Batman',
);
});

it('should throw an error on 500 response', async () => {
expect(requestController.delete('http://example.com/delete500Error')).rejects.toThrowError(
'[C0006] Request failed, Internal Server Error(500): I am not Batman',
);
});

it('should throw an error on 503 response', async () => {
expect(requestController.delete('http://example.com/delete503Error')).rejects.toThrowError(
'[C0006] Request failed, Service Unavailable(503): I am not Batman',
);
});

it('should handle multiple messages in an error response', async () => {
expect(requestController.delete('http://example.com/delete400MultiMessageError')).rejects.toThrowError(
'[C0006] Request failed, Bad Request(400): I am not Batman, I am not superman',
);
});

it('should handle empty error response', async () => {
expect(requestController.delete('http://example.com/delete400ErrorEmptyJson')).rejects.toThrowError(
'[C0006] Request failed, Bad Request(400): Unknown error (no error info returned from API)',
);
});

it('should handle empty error response', async () => {
expect(requestController.delete('http://example.com/delete400ErrorEmpty')).rejects.toThrowError(
'[C0006] Request failed, Bad Request(400): Unknown error (no error info returned from API)',
);
});

it('should send the correct searchParams', async () => {
const result = await requestController.delete('http://example.com/deleteWithSearchParams', {
name: 'Batman',
age: '30',
});

expect(result).toStrictEqual({ success: true });
});

it('should send the correct bodyparams', async () => {
const result = await requestController.delete('http://example.com/deleteWithBodyParams', undefined, {
name: 'Batman',
age: '30',
});

expect(result).toStrictEqual({ success: true });
});

it('should send the correct searchparams and bodyparams', async () => {
const result = await requestController.delete(
'http://example.com/deleteWithSearchAndBodyParams',
{
name: 'Batman',
},
{
age: '30',
},
);

expect(result).toStrictEqual({ success: true });
});
});
Loading

0 comments on commit 77eb29b

Please sign in to comment.