test: auth.service & auth.resolver

test: auth.resolver
This commit is contained in:
Nicolas Meienberger 2022-10-21 22:21:18 +02:00
parent 86f29b5754
commit 7f58f19416
8 changed files with 167 additions and 8 deletions

View file

@ -32,7 +32,7 @@ const Login: React.FC = () => {
const { data } = await login({ variables: { input: { username: values.email, password: values.password } } });
if (data?.login?.token) {
await localStorage.setItem('token', data.login.token);
localStorage.setItem('token', data.login.token);
}
await client.refetchQueries({ include: ['Me'] });

View file

@ -1,11 +1,12 @@
import { faker } from '@faker-js/faker';
import jwt from 'jsonwebtoken';
import { DataSource } from 'typeorm';
import TipiCache from '../../../config/TipiCache';
import { getConfig } from '../../../core/config/TipiConfig';
import { setupConnection, teardownConnection } from '../../../test/connection';
import { gcall } from '../../../test/gcall';
import { loginMutation, registerMutation } from '../../../test/mutations';
import { isConfiguredQuery, MeQuery } from '../../../test/queries';
import { isConfiguredQuery, MeQuery, refreshTokenQuery } from '../../../test/queries';
import User from '../../auth/user.entity';
import { TokenResponse } from '../auth.types';
import { createUser } from './user.factory';
@ -194,3 +195,39 @@ describe('Test: isConfigured', () => {
expect(data?.isConfigured).toBeTruthy();
});
});
describe('Test: refreshToken', () => {
const email = faker.internet.email();
let user1: User;
beforeEach(async () => {
user1 = await createUser(email);
});
it('should return a new token', async () => {
// Arrange
const session = faker.datatype.uuid();
await TipiCache.set(session, user1.id.toString());
// Act
const { data } = await gcall<{ refreshToken: TokenResponse }>({
source: refreshTokenQuery,
userId: user1.id,
session: session,
});
const decoded = jwt.verify(data?.refreshToken?.token || '', getConfig().jwtSecret) as jwt.JwtPayload;
// Assert
expect(data?.refreshToken).toBeDefined();
expect(data?.refreshToken?.token).toBeDefined();
expect(decoded).toBeDefined();
expect(decoded).not.toBeNull();
expect(decoded).toHaveProperty('id');
expect(decoded).toHaveProperty('iat');
expect(decoded).toHaveProperty('exp');
expect(decoded).toHaveProperty('session');
expect(decoded.id).toEqual(user1.id.toString());
expect(decoded.session).not.toEqual(session);
});
});

View file

@ -7,10 +7,13 @@ import { faker } from '@faker-js/faker';
import { setupConnection, teardownConnection } from '../../../test/connection';
import { DataSource } from 'typeorm';
import { setConfig } from '../../../core/config/TipiConfig';
import TipiCache from '../../../config/TipiCache';
let db: DataSource | null = null;
const TEST_SUITE = 'authservice';
jest.mock('redis');
beforeAll(async () => {
setConfig('jwtSecret', 'test');
db = await setupConnection(TEST_SUITE);
@ -117,4 +120,113 @@ describe('Register', () => {
// Assert
expect(isPasswordValid).toBe(true);
});
it('Should throw if email is invalid', async () => {
await expect(AuthService.register({ username: 'test', password: 'test' })).rejects.toThrowError('Invalid username');
});
});
describe('Test: logout', () => {
it('Should return true if there is no session to delete', async () => {
// Act
const result = await AuthService.logout();
// Assert
expect(result).toBe(true);
});
it('Should delete session from cache', async () => {
// Arrange
const session = faker.random.alphaNumeric(32);
await TipiCache.set(session, 'test');
expect(await TipiCache.get(session)).toBe('test');
// Act
const result = await AuthService.logout(session);
// Assert
expect(result).toBe(true);
expect(await TipiCache.get('session')).toBeUndefined();
});
});
describe('Test: refreshToken', () => {
it('Should return null if session is not provided', async () => {
// Act
const result = await AuthService.refreshToken();
// Assert
expect(result).toBeNull();
});
it('Should return null if session is not found in cache', async () => {
// Act
const result = await AuthService.refreshToken('test');
// Assert
expect(result).toBeNull();
});
it('Should return a new token if session is found in cache', async () => {
// Arrange
const session = faker.random.alphaNumeric(32);
await TipiCache.set(session, 'test');
// Act
const result = await AuthService.refreshToken(session);
// Assert
expect(result).not.toBeNull();
expect(result).toHaveProperty('token');
expect(result?.token).not.toBe(session);
});
it('Should delete old session from cache', async () => {
// Arrange
const session = faker.random.alphaNumeric(32);
await TipiCache.set(session, '1');
// Act
const result = await AuthService.refreshToken(session);
// Assert
expect(result).not.toBeNull();
expect(result).toHaveProperty('token');
expect(result?.token).not.toBe(session);
expect(await TipiCache.get(session)).toBeUndefined();
});
});
describe('Test: me', () => {
it('Should return null if userId is not provided', async () => {
// Act
const result = await AuthService.me();
// Assert
expect(result).toBeNull();
});
it('Should return null if user does not exist', async () => {
// Act
const result = await AuthService.me(1);
// Assert
expect(result).toBeNull();
});
it('Should return user if user exists', async () => {
// Arrange
const email = faker.internet.email();
const user = await createUser(email);
// Act
const result = await AuthService.me(user.id);
// Assert
expect(result).not.toBeNull();
expect(result).toHaveProperty('id');
expect(result).toHaveProperty('username');
expect(result).toHaveProperty('createdAt');
expect(result).toHaveProperty('updatedAt');
});
});

View file

@ -28,7 +28,10 @@ export default class AuthResolver {
@Mutation(() => Boolean)
async logout(@Ctx() { req }: MyContext): Promise<boolean> {
await AuthService.logout(req.session?.id);
if (req.session.id) {
await AuthService.logout(req.session?.id);
}
req.session.userId = undefined;
req.session.id = undefined;

View file

@ -69,9 +69,7 @@ const me = async (userId?: number): Promise<User | null> => {
return user;
};
const logout = async (session?: string): Promise<boolean> => {
if (!session) return false;
const logout = async (session: string): Promise<boolean> => {
await TipiCache.del(session);
return true;

View file

@ -8,11 +8,12 @@ interface Options {
[key: string]: any;
}>;
userId?: number;
session?: string;
}
let schema: GraphQLSchema | null = null;
export const gcall = async <T>({ source, variableValues, userId }: Options): Promise<ExecutionResult<T, { [key: string]: any }>> => {
export const gcall = async <T>({ source, variableValues, userId, session }: Options): Promise<ExecutionResult<T, { [key: string]: any }>> => {
if (!schema) {
schema = await createSchema();
}
@ -21,6 +22,6 @@ export const gcall = async <T>({ source, variableValues, userId }: Options): Pro
schema,
source,
variableValues,
contextValue: { req: { session: { userId } } },
contextValue: { req: { session: { userId, id: session } } },
}) as any;
};

View file

@ -9,6 +9,7 @@ import * as Me from './me.graphql';
import * as isConfigured from './isConfigured.graphql';
import * as systemInfo from './systemInfo.graphql';
import * as version from './version.graphql';
import * as refreshToken from './refreshToken.graphql';
export const listAppInfosQuery = print(listAppInfos);
export const getAppQuery = print(getApp);
@ -17,3 +18,4 @@ export const MeQuery = print(Me);
export const isConfiguredQuery = print(isConfigured);
export const systemInfoQuery = print(systemInfo);
export const versionQuery = print(version);
export const refreshTokenQuery = print(refreshToken);

View file

@ -0,0 +1,6 @@
# Write your query or mutation here
query RefreshToken {
refreshToken {
token
}
}