From b2bee4f556f1a00c5a0e19825a26f982ffd1d479 Mon Sep 17 00:00:00 2001 From: do0ori Date: Mon, 16 Dec 2024 23:14:15 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20OAuth=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인터페이스를 추상 클래스로 변경하여 공통 의존성 관리 개선 - OAuth 서비스 메서드 시그니처 변경 및 캡슐화 - 내부 구현 메서드들을 private으로 변경 - 공개 API를 목적에 맞게 재구성 (login, signup, logout 등) - 타입 정의 개선 및 일관성 있는 네이밍 적용 - 각 OAuth 서비스 구현체들의 코드 구조 통일 --- backend/server/src/auth/auth.service.spec.ts | 2 +- backend/server/src/auth/auth.service.ts | 23 ++---- .../src/auth/oauth/__mocks__/oauth.service.ts | 8 +- .../server/src/auth/oauth/google.service.ts | 77 +++++++++++++----- .../server/src/auth/oauth/kakao.service.ts | 81 ++++++++++++++----- .../server/src/auth/oauth/naver.service.ts | 77 +++++++++++++----- backend/server/src/auth/oauth/oauth.module.ts | 2 +- .../src/auth/oauth/oauth.service.base.ts | 47 +++++++++++ .../src/auth/oauth/oauth.service.interface.ts | 28 ------- .../performance/__mocks__/oauth.service.ts | 12 ++- 10 files changed, 248 insertions(+), 109 deletions(-) create mode 100644 backend/server/src/auth/oauth/oauth.service.base.ts delete mode 100644 backend/server/src/auth/oauth/oauth.service.interface.ts diff --git a/backend/server/src/auth/auth.service.spec.ts b/backend/server/src/auth/auth.service.spec.ts index f9238483..7e85ff95 100644 --- a/backend/server/src/auth/auth.service.spec.ts +++ b/backend/server/src/auth/auth.service.spec.ts @@ -4,7 +4,7 @@ import { JwtService } from '@nestjs/jwt'; import { Test, TestingModule } from '@nestjs/testing'; import { AuthService } from './auth.service'; -import { OauthService } from './oauth/oauth.service.interface'; +import { OauthService } from './oauth/oauth.service.base'; import { AccessTokenPayload, RefreshTokenPayload, TokenService } from './token/token.service'; import { OauthAuthorizeData } from './types/oauth-authorize-data.type'; import { OauthData } from './types/oauth-data.type'; diff --git a/backend/server/src/auth/auth.service.ts b/backend/server/src/auth/auth.service.ts index 397252c8..b5c17431 100644 --- a/backend/server/src/auth/auth.service.ts +++ b/backend/server/src/auth/auth.service.ts @@ -2,8 +2,7 @@ import { Inject, Injectable, NotFoundException, UnauthorizedException } from '@n import { ConfigService } from '@nestjs/config'; import { Transactional } from 'typeorm-transactional'; -import { KakaoService } from './oauth/kakao.service'; -import { OauthService } from './oauth/oauth.service.interface'; +import { OauthService } from './oauth/oauth.service.base'; import { AccessTokenPayload, RefreshTokenPayload, TokenService } from './token/token.service'; import { AuthData } from './types/auth-data.type'; import { OauthAuthorizeData } from './types/oauth-authorize-data.type'; @@ -38,13 +37,11 @@ export class AuthService { async login({ authorizeCode, provider }: OauthAuthorizeData): Promise { const oauthService = this.getOauthService(provider); - const { access_token: oauthAccessToken, refresh_token: oauthRefreshToken } = await oauthService.requestToken( + const { oauthAccessToken, oauthRefreshToken, oauthId } = await oauthService.login( authorizeCode, this.REDIRECT_URI, ); - const { oauthId } = await oauthService.requestUserInfo(oauthAccessToken); - const refreshToken = await this.tokenService.signRefreshToken(oauthId, provider); this.logger.debug('login - signRefreshToken', { refreshToken }); @@ -70,7 +67,7 @@ export class AuthService { async signup({ oauthAccessToken, oauthRefreshToken, provider }: OauthData): Promise { const oauthService = this.getOauthService(provider); - const { oauthId, oauthNickname, email } = await oauthService.requestUserInfo(oauthAccessToken); + const { oauthId, oauthNickname, email } = await oauthService.signup(oauthAccessToken); const profileImageUrl = this.S3_PROFILE_IMAGE_PATH; const refreshToken = await this.tokenService.signRefreshToken(oauthId, provider); @@ -98,9 +95,7 @@ export class AuthService { const { oauthAccessToken } = await this.usersService.findOne({ where: { id: userId } }); this.logger.debug('logout - oauthAccessToken', { oauthAccessToken }); - if (provider === 'kakao') { - await (oauthService as KakaoService).requestTokenExpiration(oauthAccessToken); - } + await oauthService.logout(oauthAccessToken); } async reissueTokens({ oauthId, provider }: RefreshTokenPayload): Promise { @@ -112,11 +107,11 @@ export class AuthService { }); const [ - { access_token: newOauthAccessToken, refresh_token: newOauthRefreshToken }, + { oauthAccessToken: newOauthAccessToken, oauthRefreshToken: newOauthRefreshToken }, newAccessToken, newRefreshToken, ] = await Promise.all([ - oauthService.requestTokenRefresh(oauthRefreshToken), + oauthService.reissueTokens(oauthRefreshToken), this.tokenService.signAccessToken(userId, provider), this.tokenService.signRefreshToken(oauthId, provider), ]); @@ -137,11 +132,7 @@ export class AuthService { const { oauthAccessToken } = await this.usersService.findOne({ where: { id: userId } }); - if (provider === 'kakao') { - await (oauthService as KakaoService).requestUnlink(oauthAccessToken); - } else { - await oauthService.requestTokenExpiration(oauthAccessToken); - } + await oauthService.deactivate(oauthAccessToken); await this.deleteUserData(userId); } diff --git a/backend/server/src/auth/oauth/__mocks__/oauth.service.ts b/backend/server/src/auth/oauth/__mocks__/oauth.service.ts index 5dca161c..87725323 100644 --- a/backend/server/src/auth/oauth/__mocks__/oauth.service.ts +++ b/backend/server/src/auth/oauth/__mocks__/oauth.service.ts @@ -1,17 +1,17 @@ -import { RequestToken, RequestTokenRefresh, RequestUserInfo } from '../oauth.service.interface'; +import { RequestTokenRefreshResponse, RequestTokenResponse, UserInfo } from '../oauth.service.base'; export const MockOauthService = { requestToken: jest.fn().mockResolvedValue({ access_token: 'mock_oauth_access_token', refresh_token: 'mock_oauth_refresh_token', - } as RequestToken), + } as RequestTokenResponse), requestUserInfo: jest.fn().mockResolvedValue({ oauthId: '12345', oauthNickname: 'mock_oauth_nickname', email: 'mock_email@example.com', profileImageUrl: 'mock_profile_image.jpg', - } as RequestUserInfo), + } as UserInfo), requestTokenExpiration: jest.fn(), @@ -20,5 +20,5 @@ export const MockOauthService = { requestTokenRefresh: jest.fn().mockResolvedValue({ access_token: 'new_mock_oauth_access_token', refresh_token: 'new_mock_oauth_refresh_token', - } as RequestTokenRefresh), + } as RequestTokenRefreshResponse), }; diff --git a/backend/server/src/auth/oauth/google.service.ts b/backend/server/src/auth/oauth/google.service.ts index e0bcf001..f89934cd 100644 --- a/backend/server/src/auth/oauth/google.service.ts +++ b/backend/server/src/auth/oauth/google.service.ts @@ -4,11 +4,18 @@ import { ConfigService } from '@nestjs/config'; import axios from 'axios'; import { firstValueFrom } from 'rxjs'; -import { OauthService, RequestToken, RequestTokenRefresh, RequestUserInfo } from './oauth.service.interface'; +import { + OauthLoginData, + OauthReissueData, + OauthService, + OauthSignupData, + RequestTokenRefreshResponse, + RequestTokenResponse, +} from './oauth.service.base'; import { WinstonLoggerService } from '../../common/logger/winstonLogger.service'; -interface TokenResponse { +interface TokenResponse extends RequestTokenResponse { access_token: string; expires_in: number; refresh_token: string; @@ -23,7 +30,7 @@ interface UserInfoResponse { picture: string; } -interface TokenRefreshResponse { +interface TokenRefreshResponse extends RequestTokenRefreshResponse { access_token: string; expires_in: number; scope: string; @@ -31,12 +38,14 @@ interface TokenRefreshResponse { } @Injectable() -export class GoogleService implements OauthService { +export class GoogleService extends OauthService { constructor( - private readonly configService: ConfigService, - private readonly httpService: HttpService, - private readonly logger: WinstonLoggerService, - ) {} + readonly configService: ConfigService, + readonly httpService: HttpService, + readonly logger: WinstonLoggerService, + ) { + super(configService, httpService, logger); + } private readonly CLIENT_ID = this.configService.get('GOOGLE_CLIENT_ID'); private readonly CLIENT_SECRET = this.configService.get('GOOGLE_CLIENT_SECRET'); @@ -44,7 +53,44 @@ export class GoogleService implements OauthService { private readonly USER_INFO_API = this.configService.get('GOOGLE_USER_INFO_API')!; private readonly REVOKE_API = this.configService.get('GOOGLE_REVOKE_API')!; - async requestToken(authorizeCode: string, redirectURI: string): Promise { + async login(authorizeCode: string, redirectURI: string): Promise { + const tokens = await this.requestToken(authorizeCode, redirectURI); + const userInfo = await this.requestUserInfo(tokens.access_token); + + return { + oauthAccessToken: tokens.access_token, + oauthRefreshToken: tokens.refresh_token, + oauthId: userInfo.id, + oauthNickname: userInfo.name, + email: userInfo.email, + profileImageUrl: userInfo.picture, + }; + } + + async signup(oauthAccessToken: string): Promise { + const userInfo = await this.requestUserInfo(oauthAccessToken); + + return { + oauthId: userInfo.id, + oauthNickname: userInfo.name, + email: userInfo.email, + profileImageUrl: userInfo.picture, + }; + } + + async logout(_oauthAccessToken: string): Promise {} + + async reissueTokens(oauthRefreshToken: string): Promise { + const tokens = await this.requestTokenRefresh(oauthRefreshToken); + + return { oauthAccessToken: tokens.access_token, oauthRefreshToken: tokens.refresh_token }; + } + + async deactivate(oauthAccessToken: string): Promise { + await this.requestTokenExpiration(oauthAccessToken); + } + + private async requestToken(authorizeCode: string, redirectURI: string): Promise { try { const { data } = await firstValueFrom( this.httpService.post(this.TOKEN_API, { @@ -69,7 +115,7 @@ export class GoogleService implements OauthService { } } - async requestUserInfo(accessToken: string): Promise { + private async requestUserInfo(accessToken: string): Promise { try { const { data } = await firstValueFrom( this.httpService.get(this.USER_INFO_API, { @@ -81,12 +127,7 @@ export class GoogleService implements OauthService { this.logger.log('requestUserInfo', { ...data }); - return { - oauthId: data.id, - oauthNickname: data.name, - email: data.email, - profileImageUrl: data.picture, - }; + return data; } catch (error) { if (axios.isAxiosError(error) && error.response) { this.logger.error('Google: 유저 정보 조회 요청이 실패했습니다', { @@ -99,7 +140,7 @@ export class GoogleService implements OauthService { } } - async requestTokenExpiration(accessToken: string) { + private async requestTokenExpiration(accessToken: string): Promise { try { await firstValueFrom( this.httpService.post( @@ -127,7 +168,7 @@ export class GoogleService implements OauthService { } } - async requestTokenRefresh(refreshToken: string): Promise { + private async requestTokenRefresh(refreshToken: string): Promise { try { const { data } = await firstValueFrom( this.httpService.post(this.TOKEN_API, { diff --git a/backend/server/src/auth/oauth/kakao.service.ts b/backend/server/src/auth/oauth/kakao.service.ts index 8c8ec1d4..19781423 100644 --- a/backend/server/src/auth/oauth/kakao.service.ts +++ b/backend/server/src/auth/oauth/kakao.service.ts @@ -4,11 +4,18 @@ import { ConfigService } from '@nestjs/config'; import axios from 'axios'; import { firstValueFrom } from 'rxjs'; -import { OauthService, RequestToken, RequestTokenRefresh, RequestUserInfo } from './oauth.service.interface'; +import { + OauthLoginData, + OauthReissueData, + OauthService, + OauthSignupData, + RequestTokenRefreshResponse, + RequestTokenResponse, +} from './oauth.service.base'; import { WinstonLoggerService } from '../../common/logger/winstonLogger.service'; -interface TokenResponse { +interface TokenResponse extends RequestTokenResponse { token_type: string; access_token: string; id_token?: string; @@ -29,7 +36,7 @@ interface UserInfoResponse { }; } -interface TokenRefreshResponse { +interface TokenRefreshResponse extends RequestTokenRefreshResponse { token_type: string; access_token: string; expires_in: number; @@ -38,12 +45,14 @@ interface TokenRefreshResponse { } @Injectable() -export class KakaoService implements OauthService { +export class KakaoService extends OauthService { constructor( - private readonly configService: ConfigService, - private readonly httpService: HttpService, - private readonly logger: WinstonLoggerService, - ) {} + readonly configService: ConfigService, + readonly httpService: HttpService, + readonly logger: WinstonLoggerService, + ) { + super(configService, httpService, logger); + } private readonly CLIENT_ID = this.configService.get('KAKAO_CLIENT_ID'); private readonly CLIENT_SECRET = this.configService.get('KAKAO_CLIENT_SECRET'); @@ -52,7 +61,46 @@ export class KakaoService implements OauthService { private readonly LOGOUT_API = this.configService.get('KAKAO_LOGOUT_API')!; private readonly UNLINK_API = this.configService.get('KAKAO_UNLINK_API')!; - async requestToken(authorizeCode: string, redirectURI: string): Promise { + async login(authorizeCode: string, redirectURI: string): Promise { + const tokens = await this.requestToken(authorizeCode, redirectURI); + const userInfo = await this.requestUserInfo(tokens.access_token); + + return { + oauthAccessToken: tokens.access_token, + oauthRefreshToken: tokens.refresh_token, + oauthId: userInfo.id.toString(), + oauthNickname: userInfo.properties.nickname, + email: userInfo.kakao_account.email, + profileImageUrl: userInfo.properties.profile_image, + }; + } + + async signup(oauthAccessToken: string): Promise { + const userInfo = await this.requestUserInfo(oauthAccessToken); + + return { + oauthId: userInfo.id.toString(), + oauthNickname: userInfo.properties.nickname, + email: userInfo.kakao_account.email, + profileImageUrl: userInfo.properties.profile_image, + }; + } + + async logout(oauthAccessToken: string): Promise { + await this.requestTokenExpiration(oauthAccessToken); + } + + async reissueTokens(oauthRefreshToken: string): Promise { + const tokens = await this.requestTokenRefresh(oauthRefreshToken); + + return { oauthAccessToken: tokens.access_token, oauthRefreshToken: tokens.refresh_token }; + } + + async deactivate(oauthAccessToken: string): Promise { + await this.requestUnlink(oauthAccessToken); + } + + private async requestToken(authorizeCode: string, redirectURI: string): Promise { try { const { data } = await firstValueFrom( this.httpService.post( @@ -85,7 +133,7 @@ export class KakaoService implements OauthService { } } - async requestUserInfo(accessToken: string): Promise { + private async requestUserInfo(accessToken: string): Promise { try { const { data } = await firstValueFrom( this.httpService.get(this.USER_INFO_API, { @@ -97,12 +145,7 @@ export class KakaoService implements OauthService { this.logger.log('requestUserInfo', { ...data }); - return { - oauthId: data.id.toString(), - oauthNickname: data.properties.nickname, - email: data.kakao_account.email, - profileImageUrl: data.properties.profile_image, - }; + return data; } catch (error) { if (axios.isAxiosError(error) && error.response) { this.logger.error('Kakao: 유저 정보 조회 요청이 실패했습니다', { @@ -115,7 +158,7 @@ export class KakaoService implements OauthService { } } - async requestTokenExpiration(accessToken: string) { + private async requestTokenExpiration(accessToken: string): Promise { try { await firstValueFrom( this.httpService.post<{ id: number }>( @@ -141,7 +184,7 @@ export class KakaoService implements OauthService { } } - async requestUnlink(accessToken: string) { + private async requestUnlink(accessToken: string): Promise { try { await firstValueFrom( this.httpService.post<{ id: number }>( @@ -167,7 +210,7 @@ export class KakaoService implements OauthService { } } - async requestTokenRefresh(refreshToken: string): Promise { + private async requestTokenRefresh(refreshToken: string): Promise { try { const { data } = await firstValueFrom( this.httpService.post( diff --git a/backend/server/src/auth/oauth/naver.service.ts b/backend/server/src/auth/oauth/naver.service.ts index c5ed9d39..7d48b446 100644 --- a/backend/server/src/auth/oauth/naver.service.ts +++ b/backend/server/src/auth/oauth/naver.service.ts @@ -4,11 +4,18 @@ import { ConfigService } from '@nestjs/config'; import axios from 'axios'; import { firstValueFrom } from 'rxjs'; -import { OauthService, RequestToken, RequestTokenRefresh, RequestUserInfo } from './oauth.service.interface'; +import { + OauthLoginData, + OauthReissueData, + OauthService, + OauthSignupData, + RequestTokenRefreshResponse, + RequestTokenResponse, +} from './oauth.service.base'; import { WinstonLoggerService } from '../../common/logger/winstonLogger.service'; -interface TokenResponse { +interface TokenResponse extends RequestTokenResponse { access_token: string; refresh_token: string; token_type: string; @@ -26,26 +33,65 @@ interface UserInfoResponse { }; } -interface TokenRefreshResponse { +interface TokenRefreshResponse extends RequestTokenRefreshResponse { access_token: string; token_type: string; expires_in: number; } @Injectable() -export class NaverService implements OauthService { +export class NaverService extends OauthService { constructor( - private readonly configService: ConfigService, - private readonly httpService: HttpService, - private readonly logger: WinstonLoggerService, - ) {} + readonly configService: ConfigService, + readonly httpService: HttpService, + readonly logger: WinstonLoggerService, + ) { + super(configService, httpService, logger); + } private readonly CLIENT_ID = this.configService.get('NAVER_CLIENT_ID'); private readonly CLIENT_SECRET = this.configService.get('NAVER_CLIENT_SECRET'); private readonly TOKEN_API = this.configService.get('NAVER_TOKEN_API')!; private readonly USER_INFO_API = this.configService.get('NAVER_USER_INFO_API')!; - async requestToken(authorizeCode: string): Promise { + async login(authorizeCode: string, _redirectURI: string): Promise { + const tokens = await this.requestToken(authorizeCode); + const userInfo = await this.requestUserInfo(tokens.access_token); + + return { + oauthAccessToken: tokens.access_token, + oauthRefreshToken: tokens.refresh_token, + oauthId: userInfo.response.id, + oauthNickname: userInfo.response.nickname, + email: userInfo.response.email, + profileImageUrl: userInfo.response.profile_image, + }; + } + + async signup(oauthAccessToken: string): Promise { + const userInfo = await this.requestUserInfo(oauthAccessToken); + + return { + oauthId: userInfo.response.id, + oauthNickname: userInfo.response.nickname, + email: userInfo.response.email, + profileImageUrl: userInfo.response.profile_image, + }; + } + + async logout(_oauthAccessToken: string): Promise {} + + async reissueTokens(oauthRefreshToken: string): Promise { + const tokens = await this.requestTokenRefresh(oauthRefreshToken); + + return { oauthAccessToken: tokens.access_token, oauthRefreshToken: tokens.refresh_token }; + } + + async deactivate(oauthAccessToken: string): Promise { + await this.requestTokenExpiration(oauthAccessToken); + } + + private async requestToken(authorizeCode: string): Promise { try { const { data } = await firstValueFrom( this.httpService.get(this.TOKEN_API, { @@ -72,7 +118,7 @@ export class NaverService implements OauthService { } } - async requestUserInfo(accessToken: string): Promise { + private async requestUserInfo(accessToken: string): Promise { try { const { data } = await firstValueFrom( this.httpService.get(this.USER_INFO_API, { @@ -84,12 +130,7 @@ export class NaverService implements OauthService { this.logger.log('requestUserInfo', { ...data }); - return { - oauthId: data.response.id, - oauthNickname: data.response.nickname, - email: data.response.email, - profileImageUrl: data.response.profile_image, - }; + return data; } catch (error) { if (axios.isAxiosError(error) && error.response) { this.logger.error('Naver: 유저 정보 조회 요청이 실패했습니다', { @@ -102,7 +143,7 @@ export class NaverService implements OauthService { } } - async requestTokenExpiration(accessToken: string) { + private async requestTokenExpiration(accessToken: string): Promise { try { await firstValueFrom( this.httpService.get<{ access_token: string; result: string }>(this.TOKEN_API, { @@ -127,7 +168,7 @@ export class NaverService implements OauthService { } } - async requestTokenRefresh(refreshToken: string): Promise { + private async requestTokenRefresh(refreshToken: string): Promise { try { const { data } = await firstValueFrom( this.httpService.get(this.TOKEN_API, { diff --git a/backend/server/src/auth/oauth/oauth.module.ts b/backend/server/src/auth/oauth/oauth.module.ts index bc6d06e4..eab8c094 100644 --- a/backend/server/src/auth/oauth/oauth.module.ts +++ b/backend/server/src/auth/oauth/oauth.module.ts @@ -6,7 +6,7 @@ import { ConfigService } from '@nestjs/config'; import { GoogleService } from './google.service'; import { KakaoService } from './kakao.service'; import { NaverService } from './naver.service'; -import { OauthService } from './oauth.service.interface'; +import { OauthService } from './oauth.service.base'; import { WinstonLoggerService } from '../../common/logger/winstonLogger.service'; diff --git a/backend/server/src/auth/oauth/oauth.service.base.ts b/backend/server/src/auth/oauth/oauth.service.base.ts new file mode 100644 index 00000000..237cff46 --- /dev/null +++ b/backend/server/src/auth/oauth/oauth.service.base.ts @@ -0,0 +1,47 @@ +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; + +import { WinstonLoggerService } from '../../common/logger/winstonLogger.service'; + +export interface RequestTokenResponse { + access_token: string; + refresh_token: string; + [key: string]: any; +} + +export interface RequestTokenRefreshResponse extends Omit { + refresh_token?: string; +} + +interface UserInfo { + oauthId: string; + oauthNickname: string; + email: string; + profileImageUrl: string; +} + +export interface OauthLoginData extends UserInfo { + oauthAccessToken: string; + oauthRefreshToken: string; +} + +export interface OauthSignupData extends UserInfo {} + +export interface OauthReissueData { + oauthAccessToken: string; + oauthRefreshToken?: string; +} + +export abstract class OauthService { + constructor( + protected readonly configService: ConfigService, + protected readonly httpService: HttpService, + protected readonly logger: WinstonLoggerService, + ) {} + + abstract login(authorizeCode: string, redirectURI: string): Promise; + abstract signup(oauthAccessToken: string): Promise; + abstract logout(oauthAccessToken: string): Promise; + abstract reissueTokens(oauthRefreshToken: string): Promise; + abstract deactivate(oauthAccessToken: string): Promise; +} diff --git a/backend/server/src/auth/oauth/oauth.service.interface.ts b/backend/server/src/auth/oauth/oauth.service.interface.ts deleted file mode 100644 index 3b341427..00000000 --- a/backend/server/src/auth/oauth/oauth.service.interface.ts +++ /dev/null @@ -1,28 +0,0 @@ -export interface RequestToken { - access_token: string; - refresh_token: string; - [key: string]: any; -} - -export interface RequestTokenRefresh extends Omit { - refresh_token?: string; -} - -export interface RequestUserInfo { - oauthId: string; - oauthNickname: string; - email: string; - profileImageUrl: string; -} - -export interface OauthService { - requestToken(authorizeCode: string, redirectURI?: string): Promise; - - requestUserInfo(accessToken: string): Promise; - - requestTokenExpiration(accessToken: string): Promise; - - requestUnlink?(accessToken: string): Promise; - - requestTokenRefresh(refreshToken: string): Promise; -} diff --git a/backend/server/test/performance/__mocks__/oauth.service.ts b/backend/server/test/performance/__mocks__/oauth.service.ts index b5d8eac4..de067865 100644 --- a/backend/server/test/performance/__mocks__/oauth.service.ts +++ b/backend/server/test/performance/__mocks__/oauth.service.ts @@ -1,11 +1,15 @@ -import { RequestToken, RequestTokenRefresh, RequestUserInfo } from '../../../src/auth/oauth/oauth.service.interface'; +import { + RequestTokenRefreshResponse, + RequestTokenResponse, + UserInfo, +} from '../../../src/auth/oauth/oauth.service.base'; export const MockOauthService = { requestToken: () => ({ access_token: 'mock_oauth_access_token', refresh_token: 'mock_oauth_refresh_token', - }) as RequestToken, + }) as RequestTokenResponse, requestUserInfo: () => ({ @@ -13,7 +17,7 @@ export const MockOauthService = { oauthNickname: 'mock_oauth_nickname', email: 'mock_email@example.com', profileImageUrl: 'mock_profile_image.jpg', - }) as RequestUserInfo, + }) as UserInfo, requestTokenExpiration: () => {}, @@ -23,5 +27,5 @@ export const MockOauthService = { ({ access_token: 'new_mock_oauth_access_token', refresh_token: 'new_mock_oauth_refresh_token', - }) as RequestTokenRefresh, + }) as RequestTokenRefreshResponse, }; From 9c1283e2881d7cb180968ff2bc03f32ce1b7ad9e Mon Sep 17 00:00:00 2001 From: do0ori Date: Mon, 16 Dec 2024 23:52:54 +0900 Subject: [PATCH 2/2] =?UTF-8?q?test:=20OAuth=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EB=AA=A8=ED=82=B9=20=EB=B0=A9=EC=8B=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OAuth 서비스 모킹을 새로운 인터페이스에 맞게 수정 - 내부 구현 메서드 대신 공개 API 기준으로 모킹 - 모든 mock 서비스에서 일관된 응답 구조 사용 - 테스트 데이터 구조 단순화 - 성능 테스트용 mock 서비스도 동일한 패턴으로 통일 --- backend/server/src/auth/auth.service.spec.ts | 33 +++++++------- .../src/auth/oauth/__mocks__/oauth.service.ts | 30 +++++++------ .../performance/__mocks__/oauth.service.ts | 45 +++++++++---------- 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/backend/server/src/auth/auth.service.spec.ts b/backend/server/src/auth/auth.service.spec.ts index 7e85ff95..6539947d 100644 --- a/backend/server/src/auth/auth.service.spec.ts +++ b/backend/server/src/auth/auth.service.spec.ts @@ -25,16 +25,9 @@ describe('AuthService', () => { beforeEach(async () => { mockOauthServices = new Map(); - const mockTokenResponse = { - access_token: mockUser.oauthAccessToken, - expires_in: 3600, - refresh_token: mockUser.oauthRefreshToken, - refresh_token_expires_in: 3600, - scope: 'scope', - token_type: 'bearer', - }; - - const mockUserInfo = { + const mockOauthLoginData = { + oauthAccessToken: mockUser.oauthAccessToken, + oauthRefreshToken: mockUser.oauthRefreshToken, oauthId: mockUser.oauthId, oauthNickname: 'test', email: 'test@mail.com', @@ -43,13 +36,21 @@ describe('AuthService', () => { OAUTH_PROVIDERS.forEach((provider) => { const mockOauthService = { - requestToken: jest.fn().mockResolvedValue(mockTokenResponse), - requestUserInfo: jest.fn().mockResolvedValue(mockUserInfo), - requestTokenExpiration: jest.fn().mockResolvedValue(undefined), - requestTokenRefresh: jest.fn().mockResolvedValue(mockTokenResponse), - requestUnlink: provider === 'kakao' ? jest.fn().mockResolvedValue(undefined) : undefined, + login: jest.fn().mockResolvedValue(mockOauthLoginData), + signup: jest.fn().mockResolvedValue({ + oauthId: mockUser.oauthId, + oauthNickname: 'test', + email: 'test@mail.com', + profileImageUrl: 'test.jpg', + }), + logout: jest.fn().mockResolvedValue(undefined), + reissueTokens: jest.fn().mockResolvedValue({ + oauthAccessToken: 'new_' + mockUser.oauthAccessToken, + oauthRefreshToken: 'new_' + mockUser.oauthRefreshToken, + }), + deactivate: provider === 'kakao' ? jest.fn().mockResolvedValue(undefined) : undefined, }; - mockOauthServices.set(provider, mockOauthService as OauthService); + mockOauthServices.set(provider, mockOauthService as unknown as OauthService); }); const module: TestingModule = await Test.createTestingModule({ diff --git a/backend/server/src/auth/oauth/__mocks__/oauth.service.ts b/backend/server/src/auth/oauth/__mocks__/oauth.service.ts index 87725323..e11ea001 100644 --- a/backend/server/src/auth/oauth/__mocks__/oauth.service.ts +++ b/backend/server/src/auth/oauth/__mocks__/oauth.service.ts @@ -1,24 +1,28 @@ -import { RequestTokenRefreshResponse, RequestTokenResponse, UserInfo } from '../oauth.service.base'; +import { OauthLoginData, OauthReissueData, OauthSignupData } from '../oauth.service.base'; export const MockOauthService = { - requestToken: jest.fn().mockResolvedValue({ - access_token: 'mock_oauth_access_token', - refresh_token: 'mock_oauth_refresh_token', - } as RequestTokenResponse), + login: jest.fn().mockResolvedValue({ + oauthAccessToken: 'mock_oauth_access_token', + oauthRefreshToken: 'mock_oauth_refresh_token', + oauthId: '12345', + oauthNickname: 'mock_oauth_nickname', + email: 'mock_email@example.com', + profileImageUrl: 'mock_profile_image.jpg', + } as OauthLoginData), - requestUserInfo: jest.fn().mockResolvedValue({ + signup: jest.fn().mockResolvedValue({ oauthId: '12345', oauthNickname: 'mock_oauth_nickname', email: 'mock_email@example.com', profileImageUrl: 'mock_profile_image.jpg', - } as UserInfo), + } as OauthSignupData), - requestTokenExpiration: jest.fn(), + logout: jest.fn(), - requestUnlink: jest.fn(), + reissueTokens: jest.fn().mockResolvedValue({ + oauthAccessToken: 'new_mock_oauth_access_token', + oauthRefreshToken: 'new_mock_oauth_refresh_token', + } as OauthReissueData), - requestTokenRefresh: jest.fn().mockResolvedValue({ - access_token: 'new_mock_oauth_access_token', - refresh_token: 'new_mock_oauth_refresh_token', - } as RequestTokenRefreshResponse), + deactivate: jest.fn(), }; diff --git a/backend/server/test/performance/__mocks__/oauth.service.ts b/backend/server/test/performance/__mocks__/oauth.service.ts index de067865..33290f58 100644 --- a/backend/server/test/performance/__mocks__/oauth.service.ts +++ b/backend/server/test/performance/__mocks__/oauth.service.ts @@ -1,31 +1,28 @@ -import { - RequestTokenRefreshResponse, - RequestTokenResponse, - UserInfo, -} from '../../../src/auth/oauth/oauth.service.base'; +import { OauthLoginData, OauthReissueData, OauthSignupData } from '../../../src/auth/oauth/oauth.service.base'; export const MockOauthService = { - requestToken: () => - ({ - access_token: 'mock_oauth_access_token', - refresh_token: 'mock_oauth_refresh_token', - }) as RequestTokenResponse, + login: jest.fn().mockResolvedValue({ + oauthAccessToken: 'mock_oauth_access_token', + oauthRefreshToken: 'mock_oauth_refresh_token', + oauthId: '12345', + oauthNickname: 'mock_oauth_nickname', + email: 'mock_email@example.com', + profileImageUrl: 'mock_profile_image.jpg', + } as OauthLoginData), - requestUserInfo: () => - ({ - oauthId: '12345', - oauthNickname: 'mock_oauth_nickname', - email: 'mock_email@example.com', - profileImageUrl: 'mock_profile_image.jpg', - }) as UserInfo, + signup: jest.fn().mockResolvedValue({ + oauthId: '12345', + oauthNickname: 'mock_oauth_nickname', + email: 'mock_email@example.com', + profileImageUrl: 'mock_profile_image.jpg', + } as OauthSignupData), - requestTokenExpiration: () => {}, + logout: jest.fn(), - requestUnlink: () => {}, + reissueTokens: jest.fn().mockResolvedValue({ + oauthAccessToken: 'new_mock_oauth_access_token', + oauthRefreshToken: 'new_mock_oauth_refresh_token', + } as OauthReissueData), - requestTokenRefresh: () => - ({ - access_token: 'new_mock_oauth_access_token', - refresh_token: 'new_mock_oauth_refresh_token', - }) as RequestTokenRefreshResponse, + deactivate: jest.fn(), };