## 주요 변경사항 ### 아키텍처 개선 - Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리) - Use Case 패턴 도입으로 비즈니스 로직 캡슐화 - Repository 패턴으로 데이터 접근 추상화 - 의존성 주입 구조 개선 ### 상태 관리 최적화 - 모든 Controller에서 불필요한 상태 관리 로직 제거 - 페이지네이션 로직 통일 및 간소화 - 에러 처리 로직 개선 (에러 메시지 한글화) - 로딩 상태 관리 최적화 ### Mock 서비스 제거 - MockDataService 완전 제거 - 모든 화면을 실제 API 전용으로 전환 - 불필요한 Mock 관련 코드 정리 ### UI/UX 개선 - Overview 화면 대시보드 기능 강화 - 라이선스 만료 알림 위젯 추가 - 사이드바 네비게이션 개선 - 일관된 UI 컴포넌트 사용 ### 코드 품질 - 중복 코드 제거 및 함수 추출 - 파일별 책임 분리 명확화 - 테스트 코드 업데이트 ## 영향 범위 - 모든 화면의 Controller 리팩토링 - API 통신 레이어 구조 개선 - 에러 처리 및 로깅 시스템 개선 ## 향후 계획 - 단위 테스트 커버리지 확대 - 통합 테스트 시나리오 추가 - 성능 모니터링 도구 통합
231 lines
6.5 KiB
Dart
231 lines
6.5 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
import 'package:mockito/annotations.dart';
|
|
import 'package:dartz/dartz.dart';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:superport/domain/usecases/auth/login_usecase.dart';
|
|
import 'package:superport/domain/usecases/base_usecase.dart';
|
|
import 'package:superport/services/auth_service.dart';
|
|
import 'package:superport/data/models/auth/login_request.dart';
|
|
import 'package:superport/data/models/auth/login_response.dart';
|
|
import 'package:superport/data/models/auth/auth_user.dart';
|
|
import 'package:superport/core/utils/error_handler.dart';
|
|
|
|
import 'login_usecase_test.mocks.dart';
|
|
|
|
@GenerateMocks([AuthService])
|
|
void main() {
|
|
late LoginUseCase loginUseCase;
|
|
late MockAuthService mockAuthService;
|
|
|
|
setUp(() {
|
|
mockAuthService = MockAuthService();
|
|
loginUseCase = LoginUseCase(mockAuthService);
|
|
});
|
|
|
|
group('LoginUseCase', () {
|
|
const tEmail = 'test@example.com';
|
|
const tPassword = 'password123!';
|
|
const tInvalidEmail = 'invalid-email';
|
|
const tEmptyPassword = '';
|
|
|
|
final tLoginRequest = LoginRequest(
|
|
email: tEmail,
|
|
password: tPassword,
|
|
);
|
|
|
|
final tLoginResponse = LoginResponse(
|
|
accessToken: 'test_access_token',
|
|
refreshToken: 'test_refresh_token',
|
|
tokenType: 'Bearer',
|
|
expiresIn: 3600,
|
|
user: AuthUser(
|
|
id: 1,
|
|
username: 'testuser',
|
|
email: tEmail,
|
|
name: 'Test User',
|
|
role: 'U',
|
|
),
|
|
);
|
|
|
|
test('로그인 성공 시 Right(LoginResponse) 반환', () async {
|
|
// arrange
|
|
when(mockAuthService.login(any))
|
|
.thenAnswer((_) async => tLoginResponse);
|
|
|
|
// act
|
|
final result = await loginUseCase(
|
|
LoginParams(email: tEmail, password: tPassword),
|
|
);
|
|
|
|
// assert
|
|
expect(result, Right(tLoginResponse));
|
|
verify(mockAuthService.login(argThat(
|
|
predicate<LoginRequest>((req) =>
|
|
req.email == tEmail && req.password == tPassword),
|
|
))).called(1);
|
|
verifyNoMoreInteractions(mockAuthService);
|
|
});
|
|
|
|
test('잘못된 이메일 형식 입력 시 ValidationFailure 반환', () async {
|
|
// act
|
|
final result = await loginUseCase(
|
|
LoginParams(email: tInvalidEmail, password: tPassword),
|
|
);
|
|
|
|
// assert
|
|
expect(result.isLeft(), true);
|
|
result.fold(
|
|
(failure) {
|
|
expect(failure, isA<AppFailure>());
|
|
expect(failure.message, '올바른 이메일 형식이 아닙니다.');
|
|
},
|
|
(_) => fail('Should return failure'),
|
|
);
|
|
verifyNever(mockAuthService.login(any));
|
|
});
|
|
|
|
test('빈 비밀번호 입력 시 ValidationFailure 반환', () async {
|
|
// act
|
|
final result = await loginUseCase(
|
|
LoginParams(email: tEmail, password: tEmptyPassword),
|
|
);
|
|
|
|
// assert
|
|
expect(result.isLeft(), true);
|
|
result.fold(
|
|
(failure) {
|
|
expect(failure, isA<AppFailure>());
|
|
expect(failure.message, '비밀번호를 입력해주세요.');
|
|
},
|
|
(_) => fail('Should return failure'),
|
|
);
|
|
verifyNever(mockAuthService.login(any));
|
|
});
|
|
|
|
test('401 에러 시 AuthFailure 반환', () async {
|
|
// arrange
|
|
final dioError = DioException(
|
|
requestOptions: RequestOptions(path: '/login'),
|
|
response: Response(
|
|
requestOptions: RequestOptions(path: '/login'),
|
|
statusCode: 401,
|
|
),
|
|
type: DioExceptionType.badResponse,
|
|
);
|
|
|
|
when(mockAuthService.login(any)).thenThrow(dioError);
|
|
|
|
// act
|
|
final result = await loginUseCase(
|
|
LoginParams(email: tEmail, password: tPassword),
|
|
);
|
|
|
|
// assert
|
|
expect(result.isLeft(), true);
|
|
result.fold(
|
|
(failure) {
|
|
expect(failure, isA<AppFailure>());
|
|
expect(failure.message, contains('인증'));
|
|
},
|
|
(_) => fail('Should return failure'),
|
|
);
|
|
});
|
|
|
|
test('네트워크 타임아웃 시 NetworkFailure 반환', () async {
|
|
// arrange
|
|
final dioError = DioException(
|
|
requestOptions: RequestOptions(path: '/login'),
|
|
type: DioExceptionType.connectionTimeout,
|
|
);
|
|
|
|
when(mockAuthService.login(any)).thenThrow(dioError);
|
|
|
|
// act
|
|
final result = await loginUseCase(
|
|
LoginParams(email: tEmail, password: tPassword),
|
|
);
|
|
|
|
// assert
|
|
expect(result.isLeft(), true);
|
|
result.fold(
|
|
(failure) {
|
|
expect(failure, isA<AppFailure>());
|
|
expect(failure.message, contains('네트워크'));
|
|
},
|
|
(_) => fail('Should return failure'),
|
|
);
|
|
});
|
|
|
|
test('서버 에러 시 ServerFailure 반환', () async {
|
|
// arrange
|
|
final dioError = DioException(
|
|
requestOptions: RequestOptions(path: '/login'),
|
|
response: Response(
|
|
requestOptions: RequestOptions(path: '/login'),
|
|
statusCode: 500,
|
|
data: {'message': '서버 내부 오류'},
|
|
),
|
|
type: DioExceptionType.badResponse,
|
|
);
|
|
|
|
when(mockAuthService.login(any)).thenThrow(dioError);
|
|
|
|
// act
|
|
final result = await loginUseCase(
|
|
LoginParams(email: tEmail, password: tPassword),
|
|
);
|
|
|
|
// assert
|
|
expect(result.isLeft(), true);
|
|
result.fold(
|
|
(failure) {
|
|
expect(failure, isA<AppFailure>());
|
|
expect(failure.message, contains('서버'));
|
|
},
|
|
(_) => fail('Should return failure'),
|
|
);
|
|
});
|
|
|
|
test('예상치 못한 에러 시 UnknownFailure 반환', () async {
|
|
// arrange
|
|
when(mockAuthService.login(any))
|
|
.thenThrow(Exception('Unexpected error'));
|
|
|
|
// act
|
|
final result = await loginUseCase(
|
|
LoginParams(email: tEmail, password: tPassword),
|
|
);
|
|
|
|
// assert
|
|
expect(result.isLeft(), true);
|
|
result.fold(
|
|
(failure) {
|
|
expect(failure, isA<AppFailure>());
|
|
expect(failure.message, contains('오류'));
|
|
},
|
|
(_) => fail('Should return failure'),
|
|
);
|
|
});
|
|
|
|
test('로그인 실패 시 (null 반환) AuthFailure 반환', () async {
|
|
// arrange
|
|
when(mockAuthService.login(any)).thenAnswer((_) async => null);
|
|
|
|
// act
|
|
final result = await loginUseCase(
|
|
LoginParams(email: tEmail, password: tPassword),
|
|
);
|
|
|
|
// assert
|
|
expect(result.isLeft(), true);
|
|
result.fold(
|
|
(failure) {
|
|
expect(failure, isA<AppFailure>());
|
|
expect(failure.message, contains('로그인'));
|
|
},
|
|
(_) => fail('Should return failure'),
|
|
);
|
|
});
|
|
});
|
|
} |