mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
refactor: auth medium tests (#19583)
This commit is contained in:
parent
3d35e65f27
commit
a2a9797fab
@ -1,146 +0,0 @@
|
|||||||
import { LoginResponseDto, login, signUpAdmin } from '@immich/sdk';
|
|
||||||
import { loginDto, signupDto } from 'src/fixtures';
|
|
||||||
import { errorDto, loginResponseDto, signupResponseDto } from 'src/responses';
|
|
||||||
import { app, utils } from 'src/utils';
|
|
||||||
import request from 'supertest';
|
|
||||||
import { beforeEach, describe, expect, it } from 'vitest';
|
|
||||||
|
|
||||||
const { email, password } = signupDto.admin;
|
|
||||||
|
|
||||||
describe(`/auth/admin-sign-up`, () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await utils.resetDatabase();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /auth/admin-sign-up', () => {
|
|
||||||
it(`should sign up the admin`, async () => {
|
|
||||||
const { status, body } = await request(app).post('/auth/admin-sign-up').send(signupDto.admin);
|
|
||||||
expect(status).toBe(201);
|
|
||||||
expect(body).toEqual(signupResponseDto.admin);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not allow a second admin to sign up', async () => {
|
|
||||||
await signUpAdmin({ signUpDto: signupDto.admin });
|
|
||||||
|
|
||||||
const { status, body } = await request(app).post('/auth/admin-sign-up').send(signupDto.admin);
|
|
||||||
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.alreadyHasAdmin);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('/auth/*', () => {
|
|
||||||
let admin: LoginResponseDto;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await utils.resetDatabase();
|
|
||||||
await signUpAdmin({ signUpDto: signupDto.admin });
|
|
||||||
admin = await login({ loginCredentialDto: loginDto.admin });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(`POST /auth/login`, () => {
|
|
||||||
it('should reject an incorrect password', async () => {
|
|
||||||
const { status, body } = await request(app).post('/auth/login').send({ email, password: 'incorrect' });
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.incorrectLogin);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should accept a correct password', async () => {
|
|
||||||
const { status, body, headers } = await request(app).post('/auth/login').send({ email, password });
|
|
||||||
expect(status).toBe(201);
|
|
||||||
expect(body).toEqual(loginResponseDto.admin);
|
|
||||||
|
|
||||||
const token = body.accessToken;
|
|
||||||
expect(token).toBeDefined();
|
|
||||||
|
|
||||||
const cookies = headers['set-cookie'];
|
|
||||||
expect(cookies).toHaveLength(3);
|
|
||||||
expect(cookies[0].split(';').map((item) => item.trim())).toEqual([
|
|
||||||
`immich_access_token=${token}`,
|
|
||||||
'Max-Age=34560000',
|
|
||||||
'Path=/',
|
|
||||||
expect.stringContaining('Expires='),
|
|
||||||
'HttpOnly',
|
|
||||||
'SameSite=Lax',
|
|
||||||
]);
|
|
||||||
expect(cookies[1].split(';').map((item) => item.trim())).toEqual([
|
|
||||||
'immich_auth_type=password',
|
|
||||||
'Max-Age=34560000',
|
|
||||||
'Path=/',
|
|
||||||
expect.stringContaining('Expires='),
|
|
||||||
'HttpOnly',
|
|
||||||
'SameSite=Lax',
|
|
||||||
]);
|
|
||||||
expect(cookies[2].split(';').map((item) => item.trim())).toEqual([
|
|
||||||
'immich_is_authenticated=true',
|
|
||||||
'Max-Age=34560000',
|
|
||||||
'Path=/',
|
|
||||||
expect.stringContaining('Expires='),
|
|
||||||
'SameSite=Lax',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /auth/validateToken', () => {
|
|
||||||
it('should reject an invalid token', async () => {
|
|
||||||
const { status, body } = await request(app).post(`/auth/validateToken`).set('Authorization', 'Bearer 123');
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.invalidToken);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should accept a valid token', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.post(`/auth/validateToken`)
|
|
||||||
.send({})
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({ authStatus: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /auth/change-password', () => {
|
|
||||||
it('should require the current password', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.post(`/auth/change-password`)
|
|
||||||
.send({ password: 'wrong-password', newPassword: 'Password1234' })
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
expect(status).toBe(400);
|
|
||||||
expect(body).toEqual(errorDto.wrongPassword);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should change the password', async () => {
|
|
||||||
const { status } = await request(app)
|
|
||||||
.post(`/auth/change-password`)
|
|
||||||
.send({ password, newPassword: 'Password1234' })
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
expect(status).toBe(200);
|
|
||||||
|
|
||||||
await login({
|
|
||||||
loginCredentialDto: {
|
|
||||||
email: 'admin@immich.cloud',
|
|
||||||
password: 'Password1234',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /auth/logout', () => {
|
|
||||||
it('should require authentication', async () => {
|
|
||||||
const { status, body } = await request(app).post(`/auth/logout`);
|
|
||||||
expect(status).toBe(401);
|
|
||||||
expect(body).toEqual(errorDto.unauthorized);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should logout the user', async () => {
|
|
||||||
const { status, body } = await request(app)
|
|
||||||
.post(`/auth/logout`)
|
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
|
||||||
expect(status).toBe(200);
|
|
||||||
expect(body).toEqual({
|
|
||||||
successful: true,
|
|
||||||
redirectUri: '/auth/login?autoLaunch=0',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -2,6 +2,7 @@ import { AuthController } from 'src/controllers/auth.controller';
|
|||||||
import { LoginResponseDto } from 'src/dtos/auth.dto';
|
import { LoginResponseDto } from 'src/dtos/auth.dto';
|
||||||
import { AuthService } from 'src/services/auth.service';
|
import { AuthService } from 'src/services/auth.service';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
import { mediumFactory } from 'test/medium.factory';
|
||||||
import { errorDto } from 'test/medium/responses';
|
import { errorDto } from 'test/medium/responses';
|
||||||
import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils';
|
import { ControllerContext, controllerSetup, mockBaseService } from 'test/utils';
|
||||||
|
|
||||||
@ -132,6 +133,50 @@ describe(AuthController.name, () => {
|
|||||||
expect(status).toEqual(201);
|
expect(status).toEqual(201);
|
||||||
expect(service.login).toHaveBeenCalledWith(expect.objectContaining({ email: 'admin@local' }), expect.anything());
|
expect(service.login).toHaveBeenCalledWith(expect.objectContaining({ email: 'admin@local' }), expect.anything());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should auth cookies on a secure connection', async () => {
|
||||||
|
const loginResponse = mediumFactory.loginResponse();
|
||||||
|
service.login.mockResolvedValue(loginResponse);
|
||||||
|
const { status, body, headers } = await request(ctx.getHttpServer())
|
||||||
|
.post('/auth/login')
|
||||||
|
.send({ name: 'admin', email: 'admin@local', password: 'password' });
|
||||||
|
|
||||||
|
expect(status).toEqual(201);
|
||||||
|
expect(body).toEqual(loginResponse);
|
||||||
|
|
||||||
|
const cookies = headers['set-cookie'];
|
||||||
|
expect(cookies).toHaveLength(3);
|
||||||
|
expect(cookies[0].split(';').map((item) => item.trim())).toEqual([
|
||||||
|
`immich_access_token=${loginResponse.accessToken}`,
|
||||||
|
'Max-Age=34560000',
|
||||||
|
'Path=/',
|
||||||
|
expect.stringContaining('Expires='),
|
||||||
|
'HttpOnly',
|
||||||
|
'SameSite=Lax',
|
||||||
|
]);
|
||||||
|
expect(cookies[1].split(';').map((item) => item.trim())).toEqual([
|
||||||
|
'immich_auth_type=password',
|
||||||
|
'Max-Age=34560000',
|
||||||
|
'Path=/',
|
||||||
|
expect.stringContaining('Expires='),
|
||||||
|
'HttpOnly',
|
||||||
|
'SameSite=Lax',
|
||||||
|
]);
|
||||||
|
expect(cookies[2].split(';').map((item) => item.trim())).toEqual([
|
||||||
|
'immich_is_authenticated=true',
|
||||||
|
'Max-Age=34560000',
|
||||||
|
'Path=/',
|
||||||
|
expect.stringContaining('Expires='),
|
||||||
|
'SameSite=Lax',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /auth/logout', () => {
|
||||||
|
it('should be an authenticated route', async () => {
|
||||||
|
await request(ctx.getHttpServer()).post('/auth/logout');
|
||||||
|
expect(ctx.authenticate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /auth/change-password', () => {
|
describe('POST /auth/change-password', () => {
|
||||||
|
@ -5,7 +5,7 @@ import { createHash, randomBytes } from 'node:crypto';
|
|||||||
import { Writable } from 'node:stream';
|
import { Writable } from 'node:stream';
|
||||||
import { AssetFace } from 'src/database';
|
import { AssetFace } from 'src/database';
|
||||||
import { Albums, AssetJobStatus, Assets, DB, Exif, FaceSearch, Memories, Person, Sessions } from 'src/db';
|
import { Albums, AssetJobStatus, Assets, DB, Exif, FaceSearch, Memories, Person, Sessions } from 'src/db';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto, LoginResponseDto } from 'src/dtos/auth.dto';
|
||||||
import { AlbumUserRole, AssetType, AssetVisibility, MemoryType, SourceType, SyncRequestType } from 'src/enum';
|
import { AlbumUserRole, AssetType, AssetVisibility, MemoryType, SourceType, SyncRequestType } from 'src/enum';
|
||||||
import { AccessRepository } from 'src/repositories/access.repository';
|
import { AccessRepository } from 'src/repositories/access.repository';
|
||||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||||
@ -17,6 +17,7 @@ import { ConfigRepository } from 'src/repositories/config.repository';
|
|||||||
import { CryptoRepository } from 'src/repositories/crypto.repository';
|
import { CryptoRepository } from 'src/repositories/crypto.repository';
|
||||||
import { DatabaseRepository } from 'src/repositories/database.repository';
|
import { DatabaseRepository } from 'src/repositories/database.repository';
|
||||||
import { EmailRepository } from 'src/repositories/email.repository';
|
import { EmailRepository } from 'src/repositories/email.repository';
|
||||||
|
import { EventRepository } from 'src/repositories/event.repository';
|
||||||
import { JobRepository } from 'src/repositories/job.repository';
|
import { JobRepository } from 'src/repositories/job.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||||
@ -305,6 +306,10 @@ const newMockRepository = <T>(key: ClassConstructor<T>) => {
|
|||||||
return automock(EmailRepository, { args: [{ setContext: () => {} }] });
|
return automock(EmailRepository, { args: [{ setContext: () => {} }] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case EventRepository: {
|
||||||
|
return automock(EventRepository, { args: [undefined, undefined, { setContext: () => {} }] });
|
||||||
|
}
|
||||||
|
|
||||||
case JobRepository: {
|
case JobRepository: {
|
||||||
return automock(JobRepository, {
|
return automock(JobRepository, {
|
||||||
args: [
|
args: [
|
||||||
@ -461,10 +466,13 @@ const sessionInsert = ({ id = newUuid(), userId, ...session }: Partial<Insertabl
|
|||||||
const userInsert = (user: Partial<Insertable<UserTable>> = {}) => {
|
const userInsert = (user: Partial<Insertable<UserTable>> = {}) => {
|
||||||
const id = user.id || newUuid();
|
const id = user.id || newUuid();
|
||||||
|
|
||||||
const defaults: Insertable<UserTable> = {
|
const defaults = {
|
||||||
email: `${id}@immich.cloud`,
|
email: `${id}@immich.cloud`,
|
||||||
name: `User ${id}`,
|
name: `User ${id}`,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
isAdmin: false,
|
||||||
|
profileImagePath: '',
|
||||||
|
shouldChangePassword: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...defaults, ...user, id };
|
return { ...defaults, ...user, id };
|
||||||
@ -513,6 +521,24 @@ const syncStream = () => {
|
|||||||
return new CustomWritable();
|
return new CustomWritable();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loginDetails = () => {
|
||||||
|
return { isSecure: false, clientIp: '', deviceType: '', deviceOS: '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginResponse = (): LoginResponseDto => {
|
||||||
|
const user = userInsert({});
|
||||||
|
return {
|
||||||
|
accessToken: 'access-token',
|
||||||
|
userId: user.id,
|
||||||
|
userEmail: user.email,
|
||||||
|
name: user.name,
|
||||||
|
profileImagePath: user.profileImagePath,
|
||||||
|
isAdmin: user.isAdmin,
|
||||||
|
shouldChangePassword: user.shouldChangePassword,
|
||||||
|
isOnboarded: false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const mediumFactory = {
|
export const mediumFactory = {
|
||||||
assetInsert,
|
assetInsert,
|
||||||
assetFaceInsert,
|
assetFaceInsert,
|
||||||
@ -524,4 +550,6 @@ export const mediumFactory = {
|
|||||||
syncStream,
|
syncStream,
|
||||||
userInsert,
|
userInsert,
|
||||||
memoryInsert,
|
memoryInsert,
|
||||||
|
loginDetails,
|
||||||
|
loginResponse,
|
||||||
};
|
};
|
||||||
|
163
server/test/medium/specs/services/auth.service.spec.ts
Normal file
163
server/test/medium/specs/services/auth.service.spec.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
import { hash } from 'bcrypt';
|
||||||
|
import { Kysely } from 'kysely';
|
||||||
|
import { DB } from 'src/db';
|
||||||
|
import { AuthType } from 'src/enum';
|
||||||
|
import { AccessRepository } from 'src/repositories/access.repository';
|
||||||
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
|
import { CryptoRepository } from 'src/repositories/crypto.repository';
|
||||||
|
import { DatabaseRepository } from 'src/repositories/database.repository';
|
||||||
|
import { EventRepository } from 'src/repositories/event.repository';
|
||||||
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { SessionRepository } from 'src/repositories/session.repository';
|
||||||
|
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||||
|
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||||
|
import { UserRepository } from 'src/repositories/user.repository';
|
||||||
|
import { AuthService } from 'src/services/auth.service';
|
||||||
|
import { mediumFactory, newMediumService } from 'test/medium.factory';
|
||||||
|
import { factory } from 'test/small.factory';
|
||||||
|
import { getKyselyDB } from 'test/utils';
|
||||||
|
|
||||||
|
let defaultDatabase: Kysely<DB>;
|
||||||
|
|
||||||
|
const setup = (db?: Kysely<DB>) => {
|
||||||
|
return newMediumService(AuthService, {
|
||||||
|
database: db || defaultDatabase,
|
||||||
|
real: [
|
||||||
|
AccessRepository,
|
||||||
|
ConfigRepository,
|
||||||
|
CryptoRepository,
|
||||||
|
DatabaseRepository,
|
||||||
|
SessionRepository,
|
||||||
|
SystemMetadataRepository,
|
||||||
|
UserRepository,
|
||||||
|
],
|
||||||
|
mock: [LoggingRepository, StorageRepository, EventRepository],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
defaultDatabase = await getKyselyDB();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(AuthService.name, () => {
|
||||||
|
describe('adminSignUp', () => {
|
||||||
|
it(`should sign up the admin`, async () => {
|
||||||
|
const { sut } = setup();
|
||||||
|
const dto = { name: 'Admin', email: 'admin@immich.cloud', password: 'password' };
|
||||||
|
|
||||||
|
await expect(sut.adminSignUp(dto)).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
email: dto.email,
|
||||||
|
name: dto.name,
|
||||||
|
isAdmin: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow a second admin to sign up', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
await ctx.newUser({ isAdmin: true });
|
||||||
|
const dto = { name: 'Admin', email: 'admin@immich.cloud', password: 'password' };
|
||||||
|
|
||||||
|
const response = sut.adminSignUp(dto);
|
||||||
|
await expect(response).rejects.toThrow(BadRequestException);
|
||||||
|
await expect(response).rejects.toThrow('The server already has an admin');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('login', () => {
|
||||||
|
it('should reject an incorrect password', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const password = 'password';
|
||||||
|
const passwordHashed = await hash(password, 10);
|
||||||
|
const { user } = await ctx.newUser({ password: passwordHashed });
|
||||||
|
const dto = { email: user.email, password: 'wrong-password' };
|
||||||
|
|
||||||
|
await expect(sut.login(dto, mediumFactory.loginDetails())).rejects.toThrow('Incorrect email or password');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accept a correct password and return a login response', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const password = 'password';
|
||||||
|
const passwordHashed = await hash(password, 10);
|
||||||
|
const { user } = await ctx.newUser({ password: passwordHashed });
|
||||||
|
const dto = { email: user.email, password };
|
||||||
|
|
||||||
|
await expect(sut.login(dto, mediumFactory.loginDetails())).resolves.toEqual({
|
||||||
|
accessToken: expect.any(String),
|
||||||
|
isAdmin: user.isAdmin,
|
||||||
|
isOnboarded: false,
|
||||||
|
name: user.name,
|
||||||
|
profileImagePath: user.profileImagePath,
|
||||||
|
userId: user.id,
|
||||||
|
userEmail: user.email,
|
||||||
|
shouldChangePassword: user.shouldChangePassword,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('logout', () => {
|
||||||
|
it('should logout', async () => {
|
||||||
|
const { sut } = setup();
|
||||||
|
const auth = factory.auth();
|
||||||
|
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
|
||||||
|
successful: true,
|
||||||
|
redirectUri: '/auth/login?autoLaunch=0',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cleanup the session', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const sessionRepo = ctx.get(SessionRepository);
|
||||||
|
const eventRepo = ctx.getMock(EventRepository);
|
||||||
|
const { user } = await ctx.newUser();
|
||||||
|
const { session } = await ctx.newSession({ userId: user.id });
|
||||||
|
const auth = factory.auth({ session, user });
|
||||||
|
eventRepo.emit.mockResolvedValue();
|
||||||
|
|
||||||
|
await expect(sessionRepo.get(session.id)).resolves.toEqual(expect.objectContaining({ id: session.id }));
|
||||||
|
await expect(sut.logout(auth, AuthType.PASSWORD)).resolves.toEqual({
|
||||||
|
successful: true,
|
||||||
|
redirectUri: '/auth/login?autoLaunch=0',
|
||||||
|
});
|
||||||
|
await expect(sessionRepo.get(session.id)).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('changePassword', () => {
|
||||||
|
it('should change the password and login with it', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const dto = { password: 'password', newPassword: 'new-password' };
|
||||||
|
const passwordHashed = await hash(dto.password, 10);
|
||||||
|
const { user } = await ctx.newUser({ password: passwordHashed });
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
|
||||||
|
const response = await sut.changePassword(auth, dto);
|
||||||
|
expect(response).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect((response as any).password).not.toBeDefined();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
sut.login({ email: user.email, password: dto.newPassword }, mediumFactory.loginDetails()),
|
||||||
|
).resolves.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate the current password', async () => {
|
||||||
|
const { sut, ctx } = setup();
|
||||||
|
const dto = { password: 'wrong-password', newPassword: 'new-password' };
|
||||||
|
const passwordHashed = await hash('password', 10);
|
||||||
|
const { user } = await ctx.newUser({ password: passwordHashed });
|
||||||
|
const auth = factory.auth({ user });
|
||||||
|
|
||||||
|
const response = sut.changePassword(auth, dto);
|
||||||
|
await expect(response).rejects.toThrow(BadRequestException);
|
||||||
|
await expect(response).rejects.toThrow('Wrong password');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user