Browse Source

test(server): change password (#1177)

Jason Rasmussen 2 years ago
parent
commit
0c896d9e59

+ 70 - 16
server/apps/immich/src/api-v1/auth/auth.service.spec.ts

@@ -1,11 +1,10 @@
 import { UserEntity } from '@app/database/entities/user.entity';
-import { BadRequestException } from '@nestjs/common';
-import { Test } from '@nestjs/testing';
+import { BadRequestException, UnauthorizedException } from '@nestjs/common';
 import * as bcrypt from 'bcrypt';
 import { AuthType } from '../../constants/jwt.constant';
 import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
 import { OAuthService } from '../oauth/oauth.service';
-import { IUserRepository, USER_REPOSITORY } from '../user/user-repository';
+import { IUserRepository } from '../user/user-repository';
 import { AuthService } from './auth.service';
 import { SignUpDto } from './dto/sign-up.dto';
 import { LoginResponseDto } from './response-dto/login-response.dto';
@@ -20,6 +19,17 @@ const fixtures = {
 const CLIENT_IP = '127.0.0.1';
 
 jest.mock('bcrypt');
+jest.mock('@nestjs/common', () => ({
+  ...jest.requireActual('@nestjs/common'),
+  Logger: jest.fn().mockReturnValue({
+    verbose: jest.fn(),
+    debug: jest.fn(),
+    log: jest.fn(),
+    info: jest.fn(),
+    warn: jest.fn(),
+    error: jest.fn(),
+  }),
+}));
 
 describe('AuthService', () => {
   let sut: AuthService;
@@ -61,19 +71,7 @@ describe('AuthService', () => {
       getLogoutEndpoint: jest.fn(),
     } as unknown as jest.Mocked<OAuthService>;
 
-    const moduleRef = await Test.createTestingModule({
-      providers: [
-        AuthService,
-        { provide: ImmichJwtService, useValue: immichJwtServiceMock },
-        { provide: OAuthService, useValue: oauthServiceMock },
-        {
-          provide: USER_REPOSITORY,
-          useValue: userRepositoryMock,
-        },
-      ],
-    }).compile();
-
-    sut = moduleRef.get(AuthService);
+    sut = new AuthService(oauthServiceMock, immichJwtServiceMock, userRepositoryMock);
   });
 
   it('should be defined', () => {
@@ -104,6 +102,62 @@ describe('AuthService', () => {
     });
   });
 
+  describe('changePassword', () => {
+    it('should change the password', async () => {
+      const authUser = { email: 'test@imimch.com' } as UserEntity;
+      const dto = { password: 'old-password', newPassword: 'new-password' };
+
+      compare.mockResolvedValue(true);
+
+      userRepositoryMock.getByEmail.mockResolvedValue({
+        email: 'test@immich.com',
+        password: 'hash-password',
+      } as UserEntity);
+
+      await sut.changePassword(authUser, dto);
+
+      expect(userRepositoryMock.getByEmail).toHaveBeenCalledWith(authUser.email, true);
+      expect(compare).toHaveBeenCalledWith('old-password', 'hash-password');
+    });
+
+    it('should throw when auth user email is not found', async () => {
+      const authUser = { email: 'test@imimch.com' } as UserEntity;
+      const dto = { password: 'old-password', newPassword: 'new-password' };
+
+      userRepositoryMock.getByEmail.mockResolvedValue(null);
+
+      await expect(sut.changePassword(authUser, dto)).rejects.toBeInstanceOf(UnauthorizedException);
+    });
+
+    it('should throw when password does not match existing password', async () => {
+      const authUser = { email: 'test@imimch.com' } as UserEntity;
+      const dto = { password: 'old-password', newPassword: 'new-password' };
+
+      compare.mockResolvedValue(false);
+
+      userRepositoryMock.getByEmail.mockResolvedValue({
+        email: 'test@immich.com',
+        password: 'hash-password',
+      } as UserEntity);
+
+      await expect(sut.changePassword(authUser, dto)).rejects.toBeInstanceOf(BadRequestException);
+    });
+
+    it('should throw when user does not have a password', async () => {
+      const authUser = { email: 'test@imimch.com' } as UserEntity;
+      const dto = { password: 'old-password', newPassword: 'new-password' };
+
+      compare.mockResolvedValue(false);
+
+      userRepositoryMock.getByEmail.mockResolvedValue({
+        email: 'test@immich.com',
+        password: '',
+      } as UserEntity);
+
+      await expect(sut.changePassword(authUser, dto)).rejects.toBeInstanceOf(BadRequestException);
+    });
+  });
+
   describe('logout', () => {
     it('should return the end session endpoint', async () => {
       oauthServiceMock.getLogoutEndpoint.mockResolvedValue('end-session-endpoint');

+ 4 - 3
server/apps/immich/src/api-v1/auth/auth.service.ts

@@ -24,6 +24,7 @@ import { UserCore } from '../user/user.core';
 @Injectable()
 export class AuthService {
   private userCore: UserCore;
+  private logger = new Logger(AuthService.name);
 
   constructor(
     private oauthService: OAuthService,
@@ -44,7 +45,7 @@ export class AuthService {
     }
 
     if (!user) {
-      Logger.warn(`Failed login attempt for user ${loginCredential.email} from ip address ${clientIp}`);
+      this.logger.warn(`Failed login attempt for user ${loginCredential.email} from ip address ${clientIp}`);
       throw new BadRequestException('Incorrect email or password');
     }
 
@@ -94,8 +95,8 @@ export class AuthService {
       });
 
       return mapAdminSignupResponse(admin);
-    } catch (e) {
-      Logger.error('e', 'signUp');
+    } catch (error) {
+      this.logger.error(`Unable to register admin user: ${error}`, (error as Error).stack);
       throw new InternalServerErrorException('Failed to register new admin user');
     }
   }

+ 12 - 15
server/apps/immich/src/api-v1/oauth/oauth.service.spec.ts

@@ -1,7 +1,7 @@
 import { SystemConfig } from '@app/database/entities/system-config.entity';
 import { UserEntity } from '@app/database/entities/user.entity';
 import { ImmichConfigService } from '@app/immich-config';
-import { BadRequestException, Logger } from '@nestjs/common';
+import { BadRequestException } from '@nestjs/common';
 import { generators, Issuer } from 'openid-client';
 import { AuthUserDto } from '../../decorators/auth-user.decorator';
 import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
@@ -32,20 +32,17 @@ const loginResponse = {
   userEmail: 'user@immich.com,',
 } as LoginResponseDto;
 
-jest.mock('@nestjs/common', () => {
-  return {
-    ...jest.requireActual('@nestjs/common'),
-    Logger: function MockLogger() {
-      Object.assign(this as Logger, {
-        verbose: jest.fn(),
-        debug: jest.fn(),
-        log: jest.fn(),
-        warn: jest.fn(),
-        error: jest.fn(),
-      });
-    },
-  };
-});
+jest.mock('@nestjs/common', () => ({
+  ...jest.requireActual('@nestjs/common'),
+  Logger: jest.fn().mockReturnValue({
+    verbose: jest.fn(),
+    debug: jest.fn(),
+    log: jest.fn(),
+    info: jest.fn(),
+    warn: jest.fn(),
+    error: jest.fn(),
+  }),
+}));
 
 describe('OAuthService', () => {
   let sut: OAuthService;