From d5797a81d2f67f7e3cda3fee8c24935e04ab4fa7 Mon Sep 17 00:00:00 2001 From: Eric Cabrel TIOGO Date: Fri, 7 Jun 2024 23:31:10 +0200 Subject: [PATCH] test(backend): write integration tests on folder --- .../auth/graphql/auth.integration.spec.ts | 8 +- .../features/auth/graphql/auth.resolvers.ts | 2 - .../graphql/folder.integration.spec.ts | 175 ++++++++++++++++++ .../folders/graphql/folder.resolvers.ts | 8 +- apps/backend/src/utils/tests/helpers.ts | 102 ++++++++-- 5 files changed, 275 insertions(+), 20 deletions(-) create mode 100644 apps/backend/src/features/folders/graphql/folder.integration.spec.ts diff --git a/apps/backend/src/features/auth/graphql/auth.integration.spec.ts b/apps/backend/src/features/auth/graphql/auth.integration.spec.ts index f9e7207e..93d6fdcd 100644 --- a/apps/backend/src/features/auth/graphql/auth.integration.spec.ts +++ b/apps/backend/src/features/auth/graphql/auth.integration.spec.ts @@ -205,7 +205,7 @@ describe('Test Authentication', () => { password: 'password', }; - const { authToken, userId } = await testHelper.createAuthenticatedUser({ ...input }); + const { authToken, user } = await testHelper.createAuthenticatedUser({ ...input }); const authenticatedUserQuery = ` query AuthenticatedUser { @@ -241,7 +241,7 @@ describe('Test Authentication', () => { expect(authenticatedUser).toMatchObject({ createdAt: expect.any(Number), email: input.email, - id: userId, + id: user.id, isEnabled: true, name: input.name, oauthProvider: 'email', @@ -259,7 +259,7 @@ describe('Test Authentication', () => { }); test('Log out the authenticated user', async () => { - const { authToken, userId } = await testHelper.createAuthenticatedUser({ + const { authToken, user } = await testHelper.createAuthenticatedUser({ email: 'jane.doe@snipcode.dev', name: 'Jane Doe', password: 'password', @@ -281,7 +281,7 @@ describe('Test Authentication', () => { const { authenticatedUser } = response.body.data; - expect(authenticatedUser.id).toEqual(userId); + expect(authenticatedUser.id).toEqual(user.id); const logoutQuery = ` mutation LogoutUser { diff --git a/apps/backend/src/features/auth/graphql/auth.resolvers.ts b/apps/backend/src/features/auth/graphql/auth.resolvers.ts index b66ceda4..2d2f1cfb 100644 --- a/apps/backend/src/features/auth/graphql/auth.resolvers.ts +++ b/apps/backend/src/features/auth/graphql/auth.resolvers.ts @@ -69,8 +69,6 @@ export class AuthResolvers { @Mutation('logoutUser') @UseGuards(AuthGuard) async logoutUser(@UserId() userId: string | undefined): Promise { - console.log('user logged out', userId); - if (!userId) { return false; } diff --git a/apps/backend/src/features/folders/graphql/folder.integration.spec.ts b/apps/backend/src/features/folders/graphql/folder.integration.spec.ts new file mode 100644 index 00000000..c05126d0 --- /dev/null +++ b/apps/backend/src/features/folders/graphql/folder.integration.spec.ts @@ -0,0 +1,175 @@ +import request from 'supertest'; + +import { TestHelper } from '../../../utils/tests/helpers'; +import { TestServer, startTestServer } from '../../../utils/tests/server'; + +const graphqlEndpoint = '/graphql'; + +describe('Test Folder', () => { + let server: TestServer; + let testHelper: TestHelper; + + beforeAll(async () => { + server = await startTestServer(); + + testHelper = new TestHelper(server.app, graphqlEndpoint); + }); + + beforeEach(async () => { + await testHelper.cleanDatabase(); + }); + + afterAll(async () => { + await server.close(); + }); + + test("Fail to create a folder when the parent folder doesn't exist", async () => { + const { authToken } = await testHelper.createAuthenticatedUser({}); + + const query = ` + mutation CreateFolder($input: CreateFolderInput!) { + createFolder(input: $input) { + id + } + } + `; + + const variables = { + input: { + name: 'My First Folder', + parentId: 'non-existent-folder-id', + }, + }; + + const response = await request(server.app.getHttpServer()) + .post(graphqlEndpoint) + .set('Authorization', authToken) + .send({ query, variables }) + .expect(200); + + const [error] = response.body.errors; + + expect(error.extensions.code).toEqual('FOLDER_NOT_FOUND'); + expect(error.message).toEqual('The folder with the id non-existent-folder-id not found'); + }); + + test('Fail to create a folder when a folder with the same name already exists in the parent folder', async () => { + const { authToken, user } = await testHelper.createAuthenticatedUser({}); + + await testHelper.createFolder(authToken, { + name: 'My First Folder', + parentId: user.rootFolderId, + }); + + const query = ` + mutation CreateFolder($input: CreateFolderInput!) { + createFolder(input: $input) { + id + } + } + `; + + const variables = { + input: { + name: 'My First Folder', + parentId: user.rootFolderId, + }, + }; + + const response = await request(server.app.getHttpServer()) + .post(graphqlEndpoint) + .set('Authorization', authToken) + .send({ query, variables }) + .expect(200); + + const [error] = response.body.errors; + + expect(error.extensions.code).toEqual('FOLDER_ALREADY_EXIST'); + expect(error.message).toEqual('A folder named "My First Folder" already exists'); + }); + + // eslint-disable-next-line jest/no-disabled-tests + test.skip("Fail to create a folder when the parent folder doesn't belong to the authenticated user", async () => { + const { authToken } = await testHelper.createAuthenticatedUser({}); + const { user: user2 } = await testHelper.createAuthenticatedUser({}); + + const query = ` + mutation CreateFolder($input: CreateFolderInput!) { + createFolder(input: $input) { + id + } + } + `; + + const variables = { + input: { + name: 'My First Folder', + parentId: user2.rootFolderId, + }, + }; + + const response = await request(server.app.getHttpServer()) + .post(graphqlEndpoint) + .set('Authorization', authToken) + .send({ query, variables }) + .expect(200); + + const [error] = response.body.errors; + + expect(error.extensions.code).toEqual('FOLDER_NOT_BELONG_TO_USER'); + expect(error.message).toEqual( + `The folder with the id ${user2.rootFolderId} does not belong to the authenticated user`, + ); + }); + + test('Successfully create a folder', async () => { + const { authToken, user } = await testHelper.createAuthenticatedUser({}); + + const query = ` + mutation CreateFolder($input: CreateFolderInput!) { + createFolder(input: $input) { + __typename + id + name + isFavorite + subFolders { + id + } + subFoldersCount + parent { + id + } + user { + id + } + } + } + `; + + const variables = { + input: { + name: 'My First Folder', + parentId: user.rootFolderId, + }, + }; + + const response = await request(server.app.getHttpServer()) + .post(graphqlEndpoint) + .set('Authorization', authToken) + .send({ query, variables }) + .expect(200); + + const { createFolder } = response.body.data; + + expect(createFolder).toMatchObject({ + __typename: 'Folder', + id: expect.any(String), + isFavorite: false, + name: 'My First Folder', + parent: { id: user.rootFolderId }, + subFolders: [], + subFoldersCount: 0, + user: { id: user.id }, + }); + }); +}); diff --git a/apps/backend/src/features/folders/graphql/folder.resolvers.ts b/apps/backend/src/features/folders/graphql/folder.resolvers.ts index fa16251f..08c93a4f 100644 --- a/apps/backend/src/features/folders/graphql/folder.resolvers.ts +++ b/apps/backend/src/features/folders/graphql/folder.resolvers.ts @@ -93,8 +93,12 @@ export class FolderResolvers { } @ResolveField() - async parent(@Parent() folder: Folder): Promise { - return this.folderService.findById(folder.id); + async parent(@Parent() folder: Folder): Promise { + if (!folder.parentId) { + return null; + } + + return this.folderService.findById(folder.parentId); } @ResolveField() diff --git a/apps/backend/src/utils/tests/helpers.ts b/apps/backend/src/utils/tests/helpers.ts index efcf5076..e74f7abb 100644 --- a/apps/backend/src/utils/tests/helpers.ts +++ b/apps/backend/src/utils/tests/helpers.ts @@ -1,16 +1,30 @@ import { INestApplication } from '@nestjs/common'; -import { randEmail, randFullName, randPassword } from '@ngneat/falso'; +import { randEmail, randFullName, randPassword, randWord } from '@ngneat/falso'; import { PrismaService, RoleName } from '@snipcode/domain'; import { generateJwtToken } from '@snipcode/utils'; import request from 'supertest'; -export type CreateUserInputArgs = { +type CreateUserInputArgs = { email: string; isEnabled: boolean; name: string; password: string | null; role: RoleName; }; + +type CreateAuthenticatedUserResult = { + authToken: string; + user: { + id: string; + rootFolderId: string; + }; +}; + +type CreateFolderArgs = { + name: string; + parentId: string; +}; + export class TestHelper { constructor( private readonly app: INestApplication, @@ -21,8 +35,15 @@ export class TestHelper { const prismaService = this.app.get(PrismaService); await prismaService.snippet.deleteMany(); + + const childFolders = await prismaService.folder.findMany({ where: { NOT: { parent: null } } }); + + await prismaService.folder.deleteMany({ where: { id: { in: childFolders.map((folder) => folder.id) } } }); + await prismaService.folder.deleteMany(); + await prismaService.session.deleteMany(); + await prismaService.user.deleteMany(); } @@ -73,17 +94,17 @@ export class TestHelper { return response.body.data.signupUser.userId; } - async createAuthenticatedUser(input: Partial): Promise<{ authToken: string; userId: string }> { + async createAuthenticatedUser(args: Partial): Promise { const createUserInput: Partial = { - ...input, - email: input.email ?? randEmail(), - isEnabled: input.isEnabled ?? true, - password: input.password ?? randPassword(), + ...args, + email: args.email ?? randEmail(), + isEnabled: args.isEnabled ?? true, + password: args.password ?? randPassword(), }; - const userId = await this.signupUser(createUserInput); + await this.signupUser(createUserInput); - const query = ` + const loginQuery = ` mutation LoginUser($email: String!, $password: String!) { loginUser(email: $email, password: $password) { token @@ -96,11 +117,68 @@ export class TestHelper { password: createUserInput.password, }; - const response = await request(this.app.getHttpServer()).post(this.graphqlEndpoint).send({ query, variables }); + const loginResponse = await request(this.app.getHttpServer()) + .post(this.graphqlEndpoint) + .send({ query: loginQuery, variables }); + + const authToken = loginResponse.body.data.loginUser.token; + + const authenticatedUserQuery = ` + query AuthenticatedUser { + authenticatedUser { + id + name + rootFolder { + id + name + } + } + } + `; + + const authenticatedUserResponse = await request(this.app.getHttpServer()) + .post(this.graphqlEndpoint) + .set('Authorization', authToken) + .send({ query: authenticatedUserQuery }) + .expect(200); + + const { authenticatedUser } = authenticatedUserResponse.body.data; return { - authToken: response.body.data.loginUser.token, - userId, + authToken, + user: { + id: authenticatedUser.id, + rootFolderId: authenticatedUser.rootFolder.id, + }, + }; + } + + async createFolder(authToken: string, args: Partial): Promise { + const createFolderInput: Partial = { + ...args, + name: args.name ?? randWord(), + }; + + const query = ` + mutation CreateFolder($input: CreateFolderInput!) { + createFolder(input: $input) { + id + } + } + `; + + const variables = { + input: { + name: createFolderInput.name, + parentId: createFolderInput.parentId, + }, }; + + const response = await request(this.app.getHttpServer()) + .post(this.graphqlEndpoint) + .set('Authorization', authToken) + .send({ query, variables }); + + return response.body.data.createFolder.id; } }