mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 10:22:53 +09:00 
			
		
		
		
	refactor: users.total metric (#23158)
* refactor: users.total metric * fix: broken test
This commit is contained in:
		@@ -1,4 +1,5 @@
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  JobName,
 | 
				
			||||||
  LoginResponseDto,
 | 
					  LoginResponseDto,
 | 
				
			||||||
  createStack,
 | 
					  createStack,
 | 
				
			||||||
  deleteUserAdmin,
 | 
					  deleteUserAdmin,
 | 
				
			||||||
@@ -327,6 +328,8 @@ describe('/admin/users', () => {
 | 
				
			|||||||
        { headers: asBearerAuth(user.accessToken) },
 | 
					        { headers: asBearerAuth(user.accessToken) },
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await utils.waitForQueueFinish(admin.accessToken, JobName.BackgroundTask);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const { status, body } = await request(app)
 | 
					      const { status, body } = await request(app)
 | 
				
			||||||
        .delete(`/admin/users/${user.userId}`)
 | 
					        .delete(`/admin/users/${user.userId}`)
 | 
				
			||||||
        .send({ force: true })
 | 
					        .send({ force: true })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,6 @@ import { ConfigRepository } from 'src/repositories/config.repository';
 | 
				
			|||||||
import { EventRepository } from 'src/repositories/event.repository';
 | 
					import { EventRepository } from 'src/repositories/event.repository';
 | 
				
			||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
 | 
					import { LoggingRepository } from 'src/repositories/logging.repository';
 | 
				
			||||||
import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository';
 | 
					import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository';
 | 
				
			||||||
import { UserRepository } from 'src/repositories/user.repository';
 | 
					 | 
				
			||||||
import { services } from 'src/services';
 | 
					import { services } from 'src/services';
 | 
				
			||||||
import { AuthService } from 'src/services/auth.service';
 | 
					import { AuthService } from 'src/services/auth.service';
 | 
				
			||||||
import { CliService } from 'src/services/cli.service';
 | 
					import { CliService } from 'src/services/cli.service';
 | 
				
			||||||
@@ -56,7 +55,6 @@ class BaseModule implements OnModuleInit, OnModuleDestroy {
 | 
				
			|||||||
    private jobService: JobService,
 | 
					    private jobService: JobService,
 | 
				
			||||||
    private telemetryRepository: TelemetryRepository,
 | 
					    private telemetryRepository: TelemetryRepository,
 | 
				
			||||||
    private authService: AuthService,
 | 
					    private authService: AuthService,
 | 
				
			||||||
    private userRepository: UserRepository,
 | 
					 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    logger.setAppName(this.worker);
 | 
					    logger.setAppName(this.worker);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
 | 
				
			|||||||
import { NotificationDto } from 'src/dtos/notification.dto';
 | 
					import { NotificationDto } from 'src/dtos/notification.dto';
 | 
				
			||||||
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
 | 
					import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
 | 
				
			||||||
import { SyncAssetExifV1, SyncAssetV1 } from 'src/dtos/sync.dto';
 | 
					import { SyncAssetExifV1, SyncAssetV1 } from 'src/dtos/sync.dto';
 | 
				
			||||||
import { ImmichWorker, MetadataKey, QueueName } from 'src/enum';
 | 
					import { ImmichWorker, MetadataKey, QueueName, UserAvatarColor, UserStatus } from 'src/enum';
 | 
				
			||||||
import { ConfigRepository } from 'src/repositories/config.repository';
 | 
					import { ConfigRepository } from 'src/repositories/config.repository';
 | 
				
			||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
 | 
					import { LoggingRepository } from 'src/repositories/logging.repository';
 | 
				
			||||||
import { JobItem, JobSource } from 'src/types';
 | 
					import { JobItem, JobSource } from 'src/types';
 | 
				
			||||||
@@ -82,11 +82,33 @@ type EventMap = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // user events
 | 
					  // user events
 | 
				
			||||||
  UserSignup: [{ notify: boolean; id: string; password?: string }];
 | 
					  UserSignup: [{ notify: boolean; id: string; password?: string }];
 | 
				
			||||||
 | 
					  UserCreate: [UserEvent];
 | 
				
			||||||
 | 
					  UserDelete: [UserEvent];
 | 
				
			||||||
 | 
					  UserRestore: [UserEvent];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // websocket events
 | 
					  // websocket events
 | 
				
			||||||
  WebsocketConnect: [{ userId: string }];
 | 
					  WebsocketConnect: [{ userId: string }];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UserEvent = {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  createdAt: Date;
 | 
				
			||||||
 | 
					  updatedAt: Date;
 | 
				
			||||||
 | 
					  deletedAt: Date | null;
 | 
				
			||||||
 | 
					  status: UserStatus;
 | 
				
			||||||
 | 
					  email: string;
 | 
				
			||||||
 | 
					  profileImagePath: string;
 | 
				
			||||||
 | 
					  isAdmin: boolean;
 | 
				
			||||||
 | 
					  shouldChangePassword: boolean;
 | 
				
			||||||
 | 
					  avatarColor: UserAvatarColor | null;
 | 
				
			||||||
 | 
					  oauthId: string;
 | 
				
			||||||
 | 
					  storageLabel: string | null;
 | 
				
			||||||
 | 
					  quotaSizeInBytes: number | null;
 | 
				
			||||||
 | 
					  quotaUsageInBytes: number;
 | 
				
			||||||
 | 
					  profileChangedAt: Date;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const serverEvents = ['ConfigUpdate'] as const;
 | 
					export const serverEvents = ['ConfigUpdate'] as const;
 | 
				
			||||||
export type ServerEvents = (typeof serverEvents)[number];
 | 
					export type ServerEvents = (typeof serverEvents)[number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -198,8 +198,8 @@ export class BaseService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async createUser(dto: Insertable<UserTable> & { email: string }): Promise<UserAdmin> {
 | 
					  async createUser(dto: Insertable<UserTable> & { email: string }): Promise<UserAdmin> {
 | 
				
			||||||
    const user = await this.userRepository.getByEmail(dto.email);
 | 
					    const exists = await this.userRepository.getByEmail(dto.email);
 | 
				
			||||||
    if (user) {
 | 
					    if (exists) {
 | 
				
			||||||
      throw new BadRequestException('User exists');
 | 
					      throw new BadRequestException('User exists');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -218,7 +218,10 @@ export class BaseService {
 | 
				
			|||||||
      payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
 | 
					      payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.telemetryRepository.api.addToGauge(`immich.users.total`, 1);
 | 
					    const user = await this.userRepository.create(payload);
 | 
				
			||||||
    return this.userRepository.create(payload);
 | 
					
 | 
				
			||||||
 | 
					    await this.eventRepository.emit('UserCreate', user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return user;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,7 @@ import { SyncService } from 'src/services/sync.service';
 | 
				
			|||||||
import { SystemConfigService } from 'src/services/system-config.service';
 | 
					import { SystemConfigService } from 'src/services/system-config.service';
 | 
				
			||||||
import { SystemMetadataService } from 'src/services/system-metadata.service';
 | 
					import { SystemMetadataService } from 'src/services/system-metadata.service';
 | 
				
			||||||
import { TagService } from 'src/services/tag.service';
 | 
					import { TagService } from 'src/services/tag.service';
 | 
				
			||||||
 | 
					import { TelemetryService } from 'src/services/telemetry.service';
 | 
				
			||||||
import { TimelineService } from 'src/services/timeline.service';
 | 
					import { TimelineService } from 'src/services/timeline.service';
 | 
				
			||||||
import { TrashService } from 'src/services/trash.service';
 | 
					import { TrashService } from 'src/services/trash.service';
 | 
				
			||||||
import { UserAdminService } from 'src/services/user-admin.service';
 | 
					import { UserAdminService } from 'src/services/user-admin.service';
 | 
				
			||||||
@@ -78,6 +79,7 @@ export const services = [
 | 
				
			|||||||
  SystemConfigService,
 | 
					  SystemConfigService,
 | 
				
			||||||
  SystemMetadataService,
 | 
					  SystemMetadataService,
 | 
				
			||||||
  TagService,
 | 
					  TagService,
 | 
				
			||||||
 | 
					  TelemetryService,
 | 
				
			||||||
  TimelineService,
 | 
					  TimelineService,
 | 
				
			||||||
  TrashService,
 | 
					  TrashService,
 | 
				
			||||||
  UserAdminService,
 | 
					  UserAdminService,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								server/src/services/telemetry.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								server/src/services/telemetry.service.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					import { OnEvent } from 'src/decorators';
 | 
				
			||||||
 | 
					import { ImmichWorker } from 'src/enum';
 | 
				
			||||||
 | 
					import { BaseService } from 'src/services/base.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class TelemetryService extends BaseService {
 | 
				
			||||||
 | 
					  @OnEvent({ name: 'AppBootstrap', workers: [ImmichWorker.Api] })
 | 
				
			||||||
 | 
					  async onBootstrap(): Promise<void> {
 | 
				
			||||||
 | 
					    const userCount = await this.userRepository.getCount();
 | 
				
			||||||
 | 
					    this.telemetryRepository.api.addToGauge('immich.users.total', userCount);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OnEvent({ name: 'UserCreate' })
 | 
				
			||||||
 | 
					  onUserCreate() {
 | 
				
			||||||
 | 
					    this.telemetryRepository.api.addToGauge(`immich.users.total`, 1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OnEvent({ name: 'UserDelete' })
 | 
				
			||||||
 | 
					  onUserDelete() {
 | 
				
			||||||
 | 
					    this.telemetryRepository.api.addToGauge(`immich.users.total`, -1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @OnEvent({ name: 'UserRestore' })
 | 
				
			||||||
 | 
					  onUserRestore() {
 | 
				
			||||||
 | 
					    this.telemetryRepository.api.addToGauge(`immich.users.total`, 1);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -103,7 +103,8 @@ export class UserAdminService extends BaseService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const status = force ? UserStatus.Removing : UserStatus.Deleted;
 | 
					    const status = force ? UserStatus.Removing : UserStatus.Deleted;
 | 
				
			||||||
    const user = await this.userRepository.update(id, { status, deletedAt: new Date() });
 | 
					    const user = await this.userRepository.update(id, { status, deletedAt: new Date() });
 | 
				
			||||||
    this.telemetryRepository.api.addToGauge(`immich.users.total`, -1);
 | 
					
 | 
				
			||||||
 | 
					    await this.eventRepository.emit('UserDelete', user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (force) {
 | 
					    if (force) {
 | 
				
			||||||
      await this.jobRepository.queue({ name: JobName.UserDelete, data: { id: user.id, force } });
 | 
					      await this.jobRepository.queue({ name: JobName.UserDelete, data: { id: user.id, force } });
 | 
				
			||||||
@@ -116,7 +117,7 @@ export class UserAdminService extends BaseService {
 | 
				
			|||||||
    await this.findOrFail(id, { withDeleted: true });
 | 
					    await this.findOrFail(id, { withDeleted: true });
 | 
				
			||||||
    await this.albumRepository.restoreAll(id);
 | 
					    await this.albumRepository.restoreAll(id);
 | 
				
			||||||
    const user = await this.userRepository.restore(id);
 | 
					    const user = await this.userRepository.restore(id);
 | 
				
			||||||
    this.telemetryRepository.api.addToGauge('immich.users.total', 1);
 | 
					    await this.eventRepository.emit('UserRestore', user);
 | 
				
			||||||
    return mapUserAdmin(user);
 | 
					    return mapUserAdmin(user);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,14 +3,14 @@ import { Updateable } from 'kysely';
 | 
				
			|||||||
import { DateTime } from 'luxon';
 | 
					import { DateTime } from 'luxon';
 | 
				
			||||||
import { SALT_ROUNDS } from 'src/constants';
 | 
					import { SALT_ROUNDS } from 'src/constants';
 | 
				
			||||||
import { StorageCore } from 'src/cores/storage.core';
 | 
					import { StorageCore } from 'src/cores/storage.core';
 | 
				
			||||||
import { OnEvent, OnJob } from 'src/decorators';
 | 
					import { OnJob } from 'src/decorators';
 | 
				
			||||||
import { AuthDto } from 'src/dtos/auth.dto';
 | 
					import { AuthDto } from 'src/dtos/auth.dto';
 | 
				
			||||||
import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
 | 
					import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
 | 
				
			||||||
import { OnboardingDto, OnboardingResponseDto } from 'src/dtos/onboarding.dto';
 | 
					import { OnboardingDto, OnboardingResponseDto } from 'src/dtos/onboarding.dto';
 | 
				
			||||||
import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
 | 
					import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
 | 
				
			||||||
import { CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto';
 | 
					import { CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto';
 | 
				
			||||||
import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUserAdmin } from 'src/dtos/user.dto';
 | 
					import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUserAdmin } from 'src/dtos/user.dto';
 | 
				
			||||||
import { CacheControl, ImmichWorker, JobName, JobStatus, QueueName, StorageFolder, UserMetadataKey } from 'src/enum';
 | 
					import { CacheControl, JobName, JobStatus, QueueName, StorageFolder, UserMetadataKey } from 'src/enum';
 | 
				
			||||||
import { UserFindOptions } from 'src/repositories/user.repository';
 | 
					import { UserFindOptions } from 'src/repositories/user.repository';
 | 
				
			||||||
import { UserTable } from 'src/schema/tables/user.table';
 | 
					import { UserTable } from 'src/schema/tables/user.table';
 | 
				
			||||||
import { BaseService } from 'src/services/base.service';
 | 
					import { BaseService } from 'src/services/base.service';
 | 
				
			||||||
@@ -213,12 +213,6 @@ export class UserService extends BaseService {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @OnEvent({ name: 'AppBootstrap', workers: [ImmichWorker.Api] })
 | 
					 | 
				
			||||||
  async onBootstrap(): Promise<void> {
 | 
					 | 
				
			||||||
    const userCount = await this.userRepository.getCount();
 | 
					 | 
				
			||||||
    this.telemetryRepository.api.addToGauge('immich.users.total', userCount);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @OnJob({ name: JobName.UserSyncUsage, queue: QueueName.BackgroundTask })
 | 
					  @OnJob({ name: JobName.UserSyncUsage, queue: QueueName.BackgroundTask })
 | 
				
			||||||
  async handleUserSyncUsage(): Promise<JobStatus> {
 | 
					  async handleUserSyncUsage(): Promise<JobStatus> {
 | 
				
			||||||
    await this.userRepository.syncUsage();
 | 
					    await this.userRepository.syncUsage();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,8 @@ beforeAll(async () => {
 | 
				
			|||||||
describe(AuthService.name, () => {
 | 
					describe(AuthService.name, () => {
 | 
				
			||||||
  describe('adminSignUp', () => {
 | 
					  describe('adminSignUp', () => {
 | 
				
			||||||
    it(`should sign up the admin`, async () => {
 | 
					    it(`should sign up the admin`, async () => {
 | 
				
			||||||
      const { sut } = setup();
 | 
					      const { sut, ctx } = setup();
 | 
				
			||||||
 | 
					      ctx.getMock(EventRepository).emit.mockResolvedValue();
 | 
				
			||||||
      const dto = { name: 'Admin', email: 'admin@immich.cloud', password: 'password' };
 | 
					      const dto = { name: 'Admin', email: 'admin@immich.cloud', password: 'password' };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await expect(sut.adminSignUp(dto)).resolves.toEqual(
 | 
					      await expect(sut.adminSignUp(dto)).resolves.toEqual(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,10 @@ import { DateTime } from 'luxon';
 | 
				
			|||||||
import { ImmichEnvironment, JobName, JobStatus } from 'src/enum';
 | 
					import { ImmichEnvironment, JobName, JobStatus } from 'src/enum';
 | 
				
			||||||
import { ConfigRepository } from 'src/repositories/config.repository';
 | 
					import { ConfigRepository } from 'src/repositories/config.repository';
 | 
				
			||||||
import { CryptoRepository } from 'src/repositories/crypto.repository';
 | 
					import { CryptoRepository } from 'src/repositories/crypto.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 { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
 | 
					import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
 | 
				
			||||||
import { TelemetryRepository } from 'src/repositories/telemetry.repository';
 | 
					 | 
				
			||||||
import { UserRepository } from 'src/repositories/user.repository';
 | 
					import { UserRepository } from 'src/repositories/user.repository';
 | 
				
			||||||
import { DB } from 'src/schema';
 | 
					import { DB } from 'src/schema';
 | 
				
			||||||
import { UserService } from 'src/services/user.service';
 | 
					import { UserService } from 'src/services/user.service';
 | 
				
			||||||
@@ -22,7 +22,7 @@ const setup = (db?: Kysely<DB>) => {
 | 
				
			|||||||
  return newMediumService(UserService, {
 | 
					  return newMediumService(UserService, {
 | 
				
			||||||
    database: db || defaultDatabase,
 | 
					    database: db || defaultDatabase,
 | 
				
			||||||
    real: [CryptoRepository, ConfigRepository, SystemMetadataRepository, UserRepository],
 | 
					    real: [CryptoRepository, ConfigRepository, SystemMetadataRepository, UserRepository],
 | 
				
			||||||
    mock: [LoggingRepository, JobRepository, TelemetryRepository],
 | 
					    mock: [LoggingRepository, JobRepository, EventRepository],
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -35,7 +35,8 @@ beforeAll(async () => {
 | 
				
			|||||||
describe(UserService.name, () => {
 | 
					describe(UserService.name, () => {
 | 
				
			||||||
  describe('create', () => {
 | 
					  describe('create', () => {
 | 
				
			||||||
    it('should create a user', async () => {
 | 
					    it('should create a user', async () => {
 | 
				
			||||||
      const { sut } = setup();
 | 
					      const { sut, ctx } = setup();
 | 
				
			||||||
 | 
					      ctx.getMock(EventRepository).emit.mockResolvedValue();
 | 
				
			||||||
      const user = mediumFactory.userInsert();
 | 
					      const user = mediumFactory.userInsert();
 | 
				
			||||||
      await expect(sut.createUser({ name: user.name, email: user.email })).resolves.toEqual(
 | 
					      await expect(sut.createUser({ name: user.name, email: user.email })).resolves.toEqual(
 | 
				
			||||||
        expect.objectContaining({ name: user.name, email: user.email }),
 | 
					        expect.objectContaining({ name: user.name, email: user.email }),
 | 
				
			||||||
@@ -43,14 +44,16 @@ describe(UserService.name, () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('should reject user with duplicate email', async () => {
 | 
					    it('should reject user with duplicate email', async () => {
 | 
				
			||||||
      const { sut } = setup();
 | 
					      const { sut, ctx } = setup();
 | 
				
			||||||
 | 
					      ctx.getMock(EventRepository).emit.mockResolvedValue();
 | 
				
			||||||
      const user = mediumFactory.userInsert();
 | 
					      const user = mediumFactory.userInsert();
 | 
				
			||||||
      await expect(sut.createUser({ email: user.email })).resolves.toMatchObject({ email: user.email });
 | 
					      await expect(sut.createUser({ email: user.email })).resolves.toMatchObject({ email: user.email });
 | 
				
			||||||
      await expect(sut.createUser({ email: user.email })).rejects.toThrow('User exists');
 | 
					      await expect(sut.createUser({ email: user.email })).rejects.toThrow('User exists');
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('should not return password', async () => {
 | 
					    it('should not return password', async () => {
 | 
				
			||||||
      const { sut } = setup();
 | 
					      const { sut, ctx } = setup();
 | 
				
			||||||
 | 
					      ctx.getMock(EventRepository).emit.mockResolvedValue();
 | 
				
			||||||
      const dto = mediumFactory.userInsert({ password: 'password' });
 | 
					      const dto = mediumFactory.userInsert({ password: 'password' });
 | 
				
			||||||
      const user = await sut.createUser({ email: dto.email, password: 'password' });
 | 
					      const user = await sut.createUser({ email: dto.email, password: 'password' });
 | 
				
			||||||
      expect((user as any).password).toBeUndefined();
 | 
					      expect((user as any).password).toBeUndefined();
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user