Files
lunchpick/test/mocks/mock_naver_api_client.dart
JiWoong Sul 85fde36157 feat: 초기 프로젝트 설정 및 LunchPick 앱 구현
LunchPick(오늘 뭐 먹Z?) Flutter 앱의 초기 구현입니다.

주요 기능:
- 네이버 지도 연동 맛집 추가
- 랜덤 메뉴 추천 시스템
- 날씨 기반 거리 조정
- 방문 기록 관리
- Bluetooth 맛집 공유
- 다크모드 지원

기술 스택:
- Flutter 3.8.1+
- Riverpod 상태 관리
- Hive 로컬 DB
- Clean Architecture

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-30 19:03:28 +09:00

231 lines
6.1 KiB
Dart

import 'package:dio/dio.dart';
import 'package:lunchpick/data/api/naver_api_client.dart';
import 'package:lunchpick/data/api/naver/naver_local_search_api.dart';
import 'package:lunchpick/core/errors/network_exceptions.dart';
/// 테스트용 모의 네이버 API 클라이언트
class MockNaverApiClient extends NaverApiClient {
final Map<String, String> _urlMappings = {};
final Map<String, String> _htmlResponses = {};
final Map<String, dynamic> _searchResults = {};
final Map<String, dynamic> _graphqlResponses = {};
/// URL 리다이렉션 매핑 설정
void setUrlRedirect(String fromUrl, String toUrl) {
_urlMappings[fromUrl] = toUrl;
}
/// HTML 응답 설정
void setHtmlResponse(String url, String html) {
_htmlResponses[url] = html;
}
/// 검색 결과 설정
void setSearchResults(String query, List<NaverLocalSearchResult> results) {
_searchResults[query] = results;
}
/// GraphQL 응답 설정
void setGraphQLResponse(Map<String, dynamic> response) {
_graphqlResponses['default'] = response;
}
/// 에러 시뮬레이션 설정
bool shouldThrowError = false;
String errorMessage = '테스트 에러';
@override
Future<String> resolveShortUrl(String shortUrl) async {
if (shouldThrowError && !_throw429) {
throw Exception(errorMessage);
}
// 설정된 매핑이 있으면 반환
if (_urlMappings.containsKey(shortUrl)) {
return _urlMappings[shortUrl]!;
}
// 기본적으로 원본 URL 반환
return shortUrl;
}
@override
Future<String> fetchMapPageHtml(String url) async {
if (shouldThrowError || _throw429) {
throw Exception(errorMessage);
}
// 설정된 HTML이 있으면 반환
if (_htmlResponses.containsKey(url)) {
return _htmlResponses[url]!;
}
// 기본 HTML 반환
return '''
<html>
<head>
<meta property="og:title" content="기본 테스트 식당">
</head>
<body>
<span class="GHAhO">기본 테스트 식당</span>
</body>
</html>
''';
}
@override
Future<List<NaverLocalSearchResult>> searchLocal({
required String query,
double? latitude,
double? longitude,
int display = 20,
int start = 1,
String sort = 'random',
}) async {
if (shouldThrowError) {
throw Exception(errorMessage);
}
// 설정된 검색 결과가 있으면 반환
if (_searchResults.containsKey(query)) {
return _searchResults[query] as List<NaverLocalSearchResult>;
}
// 기본 검색 결과 반환
return [
NaverLocalSearchResult.fromJson({
'title': '<b>$query</b> 테스트',
'link': 'https://map.naver.com/p/restaurant/1234567890',
'category': '한식>김치찌개',
'description': '테스트 설명',
'telephone': '02-1234-5678',
'address': '서울시 종로구',
'roadAddress': '서울시 종로구 세종대로 110',
'mapx': 1269784147,
'mapy': 375666805,
}),
];
}
@override
Future<Map<String, dynamic>> fetchGraphQL({
required String operationName,
Map<String, dynamic>? variables,
required String query,
}) async {
if (shouldThrowError || _throw429) {
throw Exception(errorMessage);
}
// 설정된 GraphQL 응답이 있으면 반환
if (_graphqlResponses.containsKey('default')) {
return {
'data': _graphqlResponses['default'],
};
}
// 기본 응답 반환 (places 배열 형태로 반환)
return {
'data': {
'places': [{
'id': '1',
'name': '기본 테스트 식당',
'category': '한식',
'address': '서울시 종로구',
}],
},
};
}
@override
Future<String?> fetchPlaceNameFromPcmap(String placeId) async {
if (shouldThrowError || _throw429) {
throw Exception(errorMessage);
}
// 테스트에서 설정한 값이 있으면 반환
if (_placeNames.containsKey(placeId)) {
return _placeNames[placeId];
}
// 기본값 반환
return '기본 테스트 식당';
}
// fetchPlaceNameFromPcmap용 응답 저장소
final Map<String, String> _placeNames = {};
/// 장소명 설정
void setPlaceName(String placeId, String placeName) {
_placeNames[placeId] = placeName;
}
// V2 확장 메서드들
final Map<String, String> _finalRedirectUrls = {};
final Map<String, String> _secondKoreanTexts = {};
bool _throw429 = false;
void setFinalRedirectUrl(String from, String to) {
_finalRedirectUrls[from] = to;
}
void setSecondKoreanText(String url, String text) {
_secondKoreanTexts[url] = text;
}
void setThrow429Error() {
_throw429 = true;
}
@override
Future<String> getFinalRedirectUrl(String url) async {
if (_throw429) {
throw RateLimitException(
retryAfter: '60',
originalError: '429 Too Many Requests',
);
}
await Future.delayed(const Duration(milliseconds: 500));
return _finalRedirectUrls[url] ?? url;
}
@override
Future<String?> extractSecondKoreanText(String url) async {
if (_throw429) {
throw Exception('429 Too Many Requests');
}
await Future.delayed(const Duration(milliseconds: 500));
return _secondKoreanTexts[url];
}
// fetchKoreanTextsFromPcmap 구현
final Map<String, Map<String, dynamic>> _koreanTextsData = {};
void setKoreanTextsData(String placeId, Map<String, dynamic> data) {
_koreanTextsData[placeId] = data;
}
@override
Future<Map<String, dynamic>> fetchKoreanTextsFromPcmap(String placeId) async {
if (shouldThrowError || _throw429) {
throw Exception(errorMessage);
}
// 설정된 데이터가 있으면 반환
if (_koreanTextsData.containsKey(placeId)) {
return _koreanTextsData[placeId]!;
}
// 기본 데이터 반환
return {
'success': true,
'koreanTexts': ['기본 테스트 식당'],
'jsonLdName': '기본 테스트 식당',
'apolloStateName': null,
};
}
}
// NaverLocalSearchResult는 naver_api_client.dart에