Tests: Rewrote AuthService unit test

The unit test now uses per test mocking of the necessary functions instead of one mock in the beforeEach call.
Also some tests got expanded to cover more error cases.

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2021-02-06 13:14:26 +01:00 committed by David Mehren
parent 41d6121d51
commit ba553f28da
No known key found for this signature in database
GPG key ID: 185982BA4C42B7C3

View file

@ -13,86 +13,47 @@ import { UsersModule } from '../users/users.module';
import { Identity } from '../users/identity.entity'; import { Identity } from '../users/identity.entity';
import { LoggerModule } from '../logger/logger.module'; import { LoggerModule } from '../logger/logger.module';
import { AuthToken } from './auth-token.entity'; import { AuthToken } from './auth-token.entity';
import { TokenNotValidError } from '../errors/errors'; import { NotInDBError, TokenNotValidError } from '../errors/errors';
import { Repository } from 'typeorm';
describe('AuthService', () => { describe('AuthService', () => {
let service: AuthService; let service: AuthService;
let user: User; let user: User;
let authToken: AuthToken; let authToken: AuthToken;
let userRepo: Repository<User>;
let authTokenRepo: Repository<AuthToken>;
beforeEach(async () => { beforeEach(async () => {
user = {
authTokens: [],
createdAt: new Date(),
displayName: 'hardcoded',
id: '1',
identities: [],
ownedNotes: [],
historyEntries: [],
updatedAt: new Date(),
userName: 'Testy',
};
authToken = {
accessTokenHash: '',
createdAt: new Date(),
id: 1,
label: 'testIdentifier',
keyId: 'abc',
lastUsed: null,
user: null,
validUntil: null,
};
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [ providers: [
AuthService, AuthService,
{ {
provide: getRepositoryToken(AuthToken), provide: getRepositoryToken(AuthToken),
useValue: {}, useClass: Repository,
}, },
], ],
imports: [PassportModule, UsersModule, LoggerModule], imports: [PassportModule, UsersModule, LoggerModule],
}) })
.overrideProvider(getRepositoryToken(AuthToken))
.useValue({
findOne: (): AuthToken => {
return {
...authToken,
user: user,
};
},
save: async (entity: AuthToken) => {
if (entity.lastUsed === undefined) {
expect(entity.lastUsed).toBeUndefined();
} else {
expect(entity.lastUsed.getTime()).toBeLessThanOrEqual(
new Date().getTime(),
);
}
return entity;
},
remove: async (entity: AuthToken) => {
expect(entity).toEqual({
...authToken,
user: user,
});
},
})
.overrideProvider(getRepositoryToken(Identity)) .overrideProvider(getRepositoryToken(Identity))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(User)) .overrideProvider(getRepositoryToken(User))
.useValue({ .useClass(Repository)
findOne: (): User => {
return {
...user,
authTokens: [authToken],
};
},
})
.compile(); .compile();
service = module.get<AuthService>(AuthService); service = module.get<AuthService>(AuthService);
userRepo = module.get<Repository<User>>(getRepositoryToken(User));
authTokenRepo = module.get<Repository<AuthToken>>(
getRepositoryToken(AuthToken),
);
user = User.create('hardcoded', 'Testy') as User;
authToken = AuthToken.create(
user,
'testToken',
'testKeyId',
'abc',
new Date(new Date().getTime() + 60000), // make this AuthToken valid for 1min
) as AuthToken;
}); });
it('should be defined', () => { it('should be defined', () => {
@ -121,6 +82,9 @@ describe('AuthService', () => {
describe('getTokensByUsername', () => { describe('getTokensByUsername', () => {
it('works', async () => { it('works', async () => {
jest
.spyOn(userRepo, 'findOne')
.mockResolvedValueOnce({ ...user, authTokens: [authToken] });
const tokens = await service.getTokensByUsername(user.userName); const tokens = await service.getTokensByUsername(user.userName);
expect(tokens).toHaveLength(1); expect(tokens).toHaveLength(1);
expect(tokens).toEqual([authToken]); expect(tokens).toEqual([authToken]);
@ -128,9 +92,14 @@ describe('AuthService', () => {
}); });
describe('getAuthToken', () => { describe('getAuthToken', () => {
const token = 'testToken';
it('works', async () => { it('works', async () => {
const token = 'testToken'; const accessTokenHash = await service.hashPassword(token);
authToken.accessTokenHash = await service.hashPassword(token); jest.spyOn(authTokenRepo, 'findOne').mockResolvedValueOnce({
...authToken,
user: user,
accessTokenHash: accessTokenHash,
});
const authTokenFromCall = await service.getAuthTokenAndValidate( const authTokenFromCall = await service.getAuthTokenAndValidate(
authToken.keyId, authToken.keyId,
token, token,
@ -138,12 +107,61 @@ describe('AuthService', () => {
expect(authTokenFromCall).toEqual({ expect(authTokenFromCall).toEqual({
...authToken, ...authToken,
user: user, user: user,
accessTokenHash: accessTokenHash,
});
});
describe('fails:', () => {
it('AuthToken could not be found', async () => {
jest.spyOn(authTokenRepo, 'findOne').mockResolvedValueOnce(undefined);
try {
await service.getAuthTokenAndValidate(authToken.keyId, token);
} catch (e) {
expect(e).toBeInstanceOf(NotInDBError);
}
});
it('AuthToken has wrong hash', async () => {
jest.spyOn(authTokenRepo, 'findOne').mockResolvedValueOnce({
...authToken,
user: user,
accessTokenHash: 'the wrong hash',
});
try {
await service.getAuthTokenAndValidate(authToken.keyId, token);
} catch (e) {
expect(e).toBeInstanceOf(TokenNotValidError);
}
});
it('AuthToken has wrong validUntil Date', async () => {
const accessTokenHash = await service.hashPassword(token);
jest.spyOn(authTokenRepo, 'findOne').mockResolvedValueOnce({
...authToken,
user: user,
accessTokenHash: accessTokenHash,
validUntil: new Date(1549312452000),
});
try {
await service.getAuthTokenAndValidate(authToken.keyId, token);
} catch (e) {
expect(e).toBeInstanceOf(TokenNotValidError);
}
}); });
}); });
}); });
describe('setLastUsedToken', () => { describe('setLastUsedToken', () => {
it('works', async () => { it('works', async () => {
jest.spyOn(authTokenRepo, 'findOne').mockResolvedValueOnce({
...authToken,
user: user,
lastUsed: new Date(1549312452000),
});
jest
.spyOn(authTokenRepo, 'save')
.mockImplementationOnce(async (authTokenSaved, _) => {
expect(authTokenSaved.keyId).toEqual(authToken.keyId);
expect(authTokenSaved.lastUsed).not.toEqual(1549312452000);
return authToken;
});
await service.setLastUsedToken(authToken.keyId); await service.setLastUsedToken(authToken.keyId);
}); });
}); });
@ -151,7 +169,21 @@ describe('AuthService', () => {
describe('validateToken', () => { describe('validateToken', () => {
it('works', async () => { it('works', async () => {
const token = 'testToken'; const token = 'testToken';
authToken.accessTokenHash = await service.hashPassword(token); const accessTokenHash = await service.hashPassword(token);
jest.spyOn(userRepo, 'findOne').mockResolvedValueOnce({
...user,
authTokens: [authToken],
});
jest.spyOn(authTokenRepo, 'findOne').mockResolvedValue({
...authToken,
user: user,
accessTokenHash: accessTokenHash,
});
jest
.spyOn(authTokenRepo, 'save')
.mockImplementationOnce(async (_, __) => {
return authToken;
});
const userByToken = await service.validateToken( const userByToken = await service.validateToken(
`${authToken.keyId}.${token}`, `${authToken.keyId}.${token}`,
); );
@ -160,34 +192,88 @@ describe('AuthService', () => {
authTokens: [authToken], authTokens: [authToken],
}); });
}); });
it('fails on too long token', () => { describe('fails:', () => {
expect( it('the secret is missing', () => {
service.validateToken(`${authToken.keyId}.${'a'.repeat(73)}`), expect(
).rejects.toBeInstanceOf(TokenNotValidError); service.validateToken(`${authToken.keyId}`),
).rejects.toBeInstanceOf(TokenNotValidError);
});
it('the secret is too long', () => {
expect(
service.validateToken(`${authToken.keyId}.${'a'.repeat(73)}`),
).rejects.toBeInstanceOf(TokenNotValidError);
});
}); });
}); });
describe('removeToken', () => { describe('removeToken', () => {
it('works', async () => { it('works', async () => {
jest.spyOn(authTokenRepo, 'findOne').mockResolvedValue({
...authToken,
user: user,
});
jest
.spyOn(authTokenRepo, 'remove')
.mockImplementationOnce(async (token, __) => {
expect(token).toEqual({
...authToken,
user: user,
});
return authToken;
});
await service.removeToken(authToken.keyId); await service.removeToken(authToken.keyId);
}); });
}); });
describe('createTokenForUser', () => { describe('createTokenForUser', () => {
it('works', async () => { describe('works', () => {
const identifier = 'identifier2'; const identifier = 'testIdentifier';
const token = await service.createTokenForUser( it('with validUntil 0', async () => {
user.userName, jest.spyOn(userRepo, 'findOne').mockResolvedValueOnce({
identifier, ...user,
0, authTokens: [authToken],
); });
expect(token.label).toEqual(identifier); jest
expect( .spyOn(authTokenRepo, 'save')
token.validUntil.getTime() - .mockImplementationOnce(async (authTokenSaved: AuthToken, _) => {
(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000), expect(authTokenSaved.lastUsed).toBeUndefined();
).toBeLessThanOrEqual(10000); return authTokenSaved;
expect(token.lastUsed).toBeNull(); });
expect(token.secret.startsWith(token.keyId)).toBeTruthy(); const token = await service.createTokenForUser(
user.userName,
identifier,
0,
);
expect(token.label).toEqual(identifier);
expect(
token.validUntil.getTime() -
(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000),
).toBeLessThanOrEqual(10000);
expect(token.lastUsed).toBeNull();
expect(token.secret.startsWith(token.keyId)).toBeTruthy();
});
it('with validUntil not 0', async () => {
jest.spyOn(userRepo, 'findOne').mockResolvedValueOnce({
...user,
authTokens: [authToken],
});
jest
.spyOn(authTokenRepo, 'save')
.mockImplementationOnce(async (authTokenSaved: AuthToken, _) => {
expect(authTokenSaved.lastUsed).toBeUndefined();
return authTokenSaved;
});
const validUntil = new Date().getTime() + 30000;
const token = await service.createTokenForUser(
user.userName,
identifier,
validUntil,
);
expect(token.label).toEqual(identifier);
expect(token.validUntil.getTime()).toEqual(validUntil);
expect(token.lastUsed).toBeNull();
expect(token.secret.startsWith(token.keyId)).toBeTruthy();
});
}); });
}); });
@ -203,6 +289,10 @@ describe('AuthService', () => {
describe('toAuthTokenDto', () => { describe('toAuthTokenDto', () => {
it('works', async () => { it('works', async () => {
const authToken = new AuthToken();
authToken.keyId = 'testKeyId';
authToken.label = 'testLabel';
authToken.createdAt = new Date();
const tokenDto = await service.toAuthTokenDto(authToken); const tokenDto = await service.toAuthTokenDto(authToken);
expect(tokenDto.keyId).toEqual(authToken.keyId); expect(tokenDto.keyId).toEqual(authToken.keyId);
expect(tokenDto.lastUsed).toBeNull(); expect(tokenDto.lastUsed).toBeNull();