[1/n]: Migrate deleteOne Rest API to use TwentyORM directly (#9784)

# This PR

- Addressing #3644 
- Migrates the `DELETE /rest/*` endpoint to use TwentyORM
- Factorizes common middleware logic into a common module

---------

Co-authored-by: martmull <martmull@hotmail.fr>
This commit is contained in:
P A C · 先生
2025-01-31 17:12:20 +02:00
committed by GitHub
parent d6788348ba
commit 66296a4787
22 changed files with 548 additions and 119 deletions

View File

@ -0,0 +1,4 @@
export const PERSON_1_ID = '777a8457-eb2d-40ac-a707-551b615b6987';
export const PERSON_2_ID = '777a8457-eb2d-40ac-a707-551b615b6988';
export const PERSON_3_ID = '777a8457-eb2d-40ac-a707-551b615b6989';
export const FAKE_PERSON_ID = '777a8457-eb2d-40ac-a707-551b615b6990';

View File

@ -0,0 +1,14 @@
export const PERSON_GQL_FIELDS = `
id
city
jobTitle
avatarUrl
intro
searchVector
name {
firstName
lastName
}
createdAt
deletedAt
`;

View File

@ -1,3 +1,9 @@
import {
PERSON_1_ID,
PERSON_2_ID,
PERSON_3_ID,
} from 'test/integration/constants/mock-person-ids.constants';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util';
@ -11,25 +17,6 @@ import { updateManyOperationFactory } from 'test/integration/graphql/utils/updat
import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
import { generateRecordName } from 'test/integration/utils/generate-record-name';
const PERSON_1_ID = '777a8457-eb2d-40ac-a707-551b615b6987';
const PERSON_2_ID = '777a8457-eb2d-40ac-a707-551b615b6988';
const PERSON_3_ID = '777a8457-eb2d-40ac-a707-551b615b6989';
const PERSON_GQL_FIELDS = `
id
city
jobTitle
avatarUrl
intro
searchVector
name {
firstName
lastName
}
createdAt
deletedAt
`;
describe('people resolvers (integration)', () => {
it('1. should create and return people', async () => {
const personCity1 = generateRecordName(PERSON_1_ID);

View File

@ -0,0 +1,107 @@
import {
FAKE_PERSON_ID,
PERSON_1_ID,
} from 'test/integration/constants/mock-person-ids.constants';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
import { findOneOperationFactory } from 'test/integration/graphql/utils/find-one-operation-factory.util';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { makeRestAPIRequest } from 'test/integration/rest/utils/make-rest-api-request.util';
import { generateRecordName } from 'test/integration/utils/generate-record-name';
describe('Core REST API Delete One endpoint', () => {
let people: any;
beforeAll(async () => {
const personCity1 = generateRecordName(PERSON_1_ID);
// TODO: move this creation to REST API when the POST method is migrated
const graphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
id: PERSON_1_ID,
city: personCity1,
},
],
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
people = response.body.data.createPeople;
expect(people.length).toBe(1);
expect(people[0].id).toBe(PERSON_1_ID);
});
afterAll(async () => {
// TODO: move this creation to REST API when the GET method is migrated
const graphqlOperation = findOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
eq: PERSON_1_ID,
},
},
});
await makeGraphqlAPIRequest(graphqlOperation)
.expect(400)
.expect((res) => {
expect(res.body.error.message).toContain(`Record not found`);
});
});
it('1a. should delete one person', async () => {
const response = await makeRestAPIRequest(
'delete',
`/people/${PERSON_1_ID}`,
);
expect(response.body.data.deletePerson.id).toBe(PERSON_1_ID);
});
it('1.b. should return a BadRequestException when trying to delete a non-existing person', async () => {
await makeRestAPIRequest('delete', `/people/${FAKE_PERSON_ID}`)
.expect(400)
.expect((res) => {
expect(res.body.messages[0]).toContain(
`Could not find any entity of type "person"`,
);
expect(res.body.error).toBe('Bad Request');
});
});
it('1.c. should return an UnauthorizedException when no token is provided', async () => {
await makeRestAPIRequest('delete', `/people/${PERSON_1_ID}`, {
authorization: '',
})
.expect(401)
.expect((res) => {
expect(res.body.error).toBe('UNAUTHENTICATED');
});
});
it('1.d. should return an UnauthorizedException when an invalid token is provided', async () => {
await makeRestAPIRequest('delete', `/people/${PERSON_1_ID}`, {
authorization: 'Bearer invalid-token',
})
.expect(401)
.expect((res) => {
expect(res.body.error).toBe('UNAUTHENTICATED');
});
});
it('1.e. should return an UnauthorizedException when an expired token is provided', async () => {
await makeRestAPIRequest('delete', `/people/${PERSON_1_ID}`, {
authorization: `Bearer ${EXPIRED_ACCESS_TOKEN}`,
})
.expect(401)
.expect((res) => {
expect(res.body.error).toBe('UNAUTHENTICATED');
expect(res.body.messages[0]).toBe('Token has expired.'); // Adjust this based on your API's error response
});
});
});

View File

@ -0,0 +1,18 @@
import { IncomingHttpHeaders } from 'http';
import request from 'supertest';
export type RestAPIRequestMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
export const makeRestAPIRequest = (
method: RestAPIRequestMethod,
path: string,
headers: IncomingHttpHeaders = {},
) => {
const client = request(`http://localhost:${APP_PORT}`);
return client[method]('/rest' + path)
.set('Authorization', `Bearer ${ACCESS_TOKEN}`)
.set({ ...headers })
.send();
};