Skip to content

Commit

Permalink
test: finish integration tests for authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
tericcabrel committed Jun 2, 2024
1 parent bf04d08 commit 8409e3a
Show file tree
Hide file tree
Showing 33 changed files with 682 additions and 191 deletions.
1 change: 1 addition & 0 deletions apps/backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ SESSION_LIFETIME=90# 90 days
SENTRY_DSN=
SENTRY_ENABLED=false
SNIPPET_RENDERER_API_URL=http://localhost:3000/dev
JWT_SECRET=jwtSecret
1 change: 1 addition & 0 deletions apps/backend/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ SESSION_LIFETIME=90
SENTRY_DSN=sentry-dsn
SENTRY_ENABLED=false
SNIPPET_RENDERER_API_URL=http://localhost:3000/dev
JWT_SECRET=jwtSecret
3 changes: 2 additions & 1 deletion apps/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ These packages are located in the folder `packages`, so you might need to change
Here are the packages used in this project:

* [@snipcode/domain](../../packages/domain)
* [@snipcode/logger](../../packages/logger-old)
* [@snipcode/embed](../../packages/embed)
* [@snipcode/utils](../../packages/utils)

## Set up the project
Expand Down Expand Up @@ -67,6 +67,7 @@ nano .env.local
| SENTRY_DSN | Sentry DSN |
| SENTRY_ENABLED | Enable/Disable Sentry |
| SNIPPET_RENDERER_API_URL | Base URL of the API (the current one) for generating the html content from a snippet |
| JWT_SECRET | The secret code for decoding JWT token generated in the application |

Start the application
```bash
Expand Down
1 change: 1 addition & 0 deletions apps/backend/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type EnvironmentVariables = {
GITHUB_CLIENT_SECRET: string;
HOST: string;
INTROSPECTION_ENABLED: string;
JWT_SECRET: string;
NODE_ENV: string;
PORT: string;
REQUEST_TIMEOUT: string;
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"gql:gen:types": "ts-node scripts/generate-graphql-types.ts",
"lint": "eslint \"{src,scripts}/**/*.ts\" --fix",
"prod": "node dist/main",
"test": "yarn workspace @snipcode/domain db:test && dotenv -e .env.test -- jest --watchAll --runInBand",
"test": "yarn workspace @snipcode/domain db:test && dotenv -e .env.test -- jest --runInBand",
"test:watch": "yarn workspace @snipcode/domain db:test && dotenv -e .env.test -- jest --watchAll --runInBand",
"test:coverage": "yarn test --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:it": "yarn test integration.spec.ts"
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/configs/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const EnvironmentVariablesSchema = z.object({
GITHUB_CLIENT_SECRET: z.string(),
HOST: z.string(),
INTROSPECTION_ENABLED: z.boolean({ coerce: true }),
JWT_SECRET: z.string(),
NODE_ENV: z.union([z.literal('development'), z.literal('production'), z.literal('test')]),
PORT: z.number({ coerce: true }).min(7000).max(8000),
SENTRY_DSN: z.string(),
Expand Down
285 changes: 274 additions & 11 deletions apps/backend/src/features/auth/graphql/auth.integration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PrismaService, RoleService, UserService } from '@snipcode/domain';
import { isValidUUIDV4 } from '@snipcode/utils';
import { PrismaService, RoleService, SessionService, UserService } from '@snipcode/domain';
import { generateJwtToken, isValidUUIDV4 } from '@snipcode/utils';
import request from 'supertest';

import { TestHelper } from '../../../utils/tests/helpers';
Expand All @@ -12,14 +12,16 @@ describe('Test Authentication', () => {
let testHelper: TestHelper;
let prismaService: PrismaService;
let roleService: RoleService;
let sessionService: SessionService;
let userService: UserService;

beforeAll(async () => {
server = await startTestServer();

prismaService = server.app.get<PrismaService>(PrismaService);
userService = server.app.get<UserService>(UserService);
roleService = server.app.get<RoleService>(RoleService);
userService = server.app.get<UserService>(UserService);
sessionService = server.app.get<SessionService>(SessionService);

testHelper = new TestHelper(prismaService, roleService, userService);
});
Expand All @@ -38,6 +40,7 @@ describe('Test Authentication', () => {
signupUser(input: $input) {
__typename
message
userId
}
}
`;
Expand All @@ -57,6 +60,7 @@ describe('Test Authentication', () => {
expect(response.body.data.signupUser).toMatchObject({
__typename: 'SignupUserResult',
message: 'Account created successfully!',
userId: expect.any(String),
});
});

Expand Down Expand Up @@ -84,8 +88,10 @@ describe('Test Authentication', () => {
.send({ query, variables })
.expect(200);

expect(response.body.errors[0].extensions.code).toEqual('EMAIL_ALREADY_TAKEN');
expect(response.body.errors[0].message).toEqual('The email address is already taken');
const [error] = response.body.errors;

expect(error.extensions.code).toEqual('EMAIL_ALREADY_TAKEN');
expect(error.message).toEqual('The email address is already taken');
});

test('Returns an error when authenticating with bad credentials', async () => {
Expand All @@ -106,8 +112,10 @@ describe('Test Authentication', () => {
.send({ query, variables })
.expect(200);

expect(response.body.errors[0].extensions.code).toEqual('LOGIN_FAILED');
expect(response.body.errors[0].message).toEqual('Invalid email address or password.');
const [error] = response.body.errors;

expect(error.extensions.code).toEqual('LOGIN_FAILED');
expect(error.message).toEqual('Invalid email address or password.');
});

test('Returns a token when authenticating with correct credentials', async () => {
Expand Down Expand Up @@ -136,8 +144,14 @@ describe('Test Authentication', () => {
.send({ query, variables })
.expect(200);

expect(response.body.data.loginUser.token).toBeDefined();
expect(isValidUUIDV4(response.body.data.loginUser.token)).toBe(true);
const { loginUser } = response.body.data;

expect(loginUser.token).toBeDefined();
expect(isValidUUIDV4(loginUser.token)).toBe(true);

const session = await sessionService.findByToken(loginUser.token);

expect(session).toBeDefined();
});

test('Returns an error message when trying to authenticate with a disabled account', async () => {
Expand Down Expand Up @@ -166,7 +180,256 @@ describe('Test Authentication', () => {
.send({ query, variables })
.expect(200);

expect(response.body.errors[0].extensions.code).toEqual('ACCOUNT_DISABLED');
expect(response.body.errors[0].message).toEqual('Your account is disabled!');
const [error] = response.body.errors;

expect(error.extensions.code).toEqual('ACCOUNT_DISABLED');
expect(error.message).toEqual('Your account is disabled!');
});

test('Returns when retrieving the authenticated user without an authentication token', async () => {
const authenticatedUserQuery = `
query AuthenticatedUser {
authenticatedUser {
id
}
}
`;

const response = await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.send({ query: authenticatedUserQuery })
.expect(200);

const [error] = response.body.errors;

expect(error.extensions.code).toEqual('UNAUTHENTICATED');
expect(error.message).toEqual('You must be authenticated to access to this resource.');
});

test('Retrieve the authenticated user', async () => {
const signUpQuery = `
mutation SignupUser($input: SignupUserInput!) {
signupUser(input: $input) {
__typename
message
userId
}
}
`;

const signUpVariables = {
input: {
email: '[email protected]',
name: 'John Doe',
password: 'password',
},
};

const signUpResponse = await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.send({ query: signUpQuery, variables: signUpVariables })
.expect(200);

const confirmationToken = generateJwtToken({
expiresIn: '1h',
payload: { userId: signUpResponse.body.data.signupUser.userId },
secret: process.env.JWT_SECRET,
});

const confirmUserQuery = `
mutation ConfirmUser($token: String!) {
confirmUser(token: $token) {
message
}
}
`;

const confirmUserVariables = {
token: confirmationToken,
};

await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.send({ query: confirmUserQuery, variables: confirmUserVariables })
.expect(200);

const loginQuery = `
mutation LoginUser($email: String!, $password: String!) {
loginUser(email: $email, password: $password) {
token
}
}
`;

const loginVariables = {
email: signUpVariables.input.email,
password: signUpVariables.input.password,
};

const loginResponse = await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.send({ query: loginQuery, variables: loginVariables })
.expect(200);

const authToken = loginResponse.body.data.loginUser.token;

const authenticatedUserQuery = `
query AuthenticatedUser {
authenticatedUser {
id
name
email
isEnabled
timezone
username
pictureUrl
role {
name
}
rootFolder {
id
name
}
createdAt
oauthProvider
}
}
`;

const response = await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.set('Authorization', authToken)
.send({ query: authenticatedUserQuery })
.expect(200);

const { authenticatedUser } = response.body.data;

expect(authenticatedUser).toMatchObject({
createdAt: expect.any(Number),
email: loginVariables.email,
id: signUpResponse.body.data.signupUser.userId,
isEnabled: true,
name: signUpVariables.input.name,
oauthProvider: 'email',
pictureUrl: null,
role: {
name: 'user',
},
rootFolder: {
id: expect.any(String),
name: `__${authenticatedUser.id}__`,
},
timezone: null,
username: expect.any(String),
});
});

test('Log out the authenticated user', async () => {
const signUpQuery = `
mutation SignupUser($input: SignupUserInput!) {
signupUser(input: $input) {
__typename
message
userId
}
}
`;

const signUpVariables = {
input: {
email: '[email protected]',
name: 'Jane Doe',
password: 'password',
},
};

const signUpResponse = await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.send({ query: signUpQuery, variables: signUpVariables })
.expect(200);

const confirmationToken = generateJwtToken({
expiresIn: '1h',
payload: { userId: signUpResponse.body.data.signupUser.userId },
secret: process.env.JWT_SECRET,
});

const confirmUserQuery = `
mutation ConfirmUser($token: String!) {
confirmUser(token: $token) {
message
}
}
`;

const confirmUserVariables = {
token: confirmationToken,
};

await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.send({ query: confirmUserQuery, variables: confirmUserVariables })
.expect(200);

const loginQuery = `
mutation LoginUser($email: String!, $password: String!) {
loginUser(email: $email, password: $password) {
token
}
}
`;

const loginVariables = {
email: signUpVariables.input.email,
password: signUpVariables.input.password,
};

const loginResponse = await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.send({ query: loginQuery, variables: loginVariables })
.expect(200);

const authToken = loginResponse.body.data.loginUser.token;

const authenticatedUserQuery = `
query AuthenticatedUser {
authenticatedUser {
id
}
}
`;

const response = await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.set('Authorization', authToken)
.send({ query: authenticatedUserQuery })
.expect(200);

const { authenticatedUser } = response.body.data;

expect(authenticatedUser.id).toEqual(signUpResponse.body.data.signupUser.userId);

const logoutQuery = `
mutation LogoutUser {
logoutUser
}
`;

await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.set('Authorization', authToken)
.send({ query: logoutQuery })
.expect(200);

const afterLogoutResponse = await request(server.app.getHttpServer())
.post(graphqlEndpoint)
.set('Authorization', authToken)
.send({ query: authenticatedUserQuery })
.expect(200);

const [error] = afterLogoutResponse.body.errors;

expect(error.extensions.code).toEqual('UNAUTHENTICATED');
expect(error.message).toEqual('You must be authenticated to access to this resource.');
});
});
Loading

0 comments on commit 8409e3a

Please sign in to comment.