feat(app): add manual entry and sharing flows

This commit is contained in:
JiWoong Sul
2025-11-19 16:36:39 +09:00
parent 5ade584370
commit 947fe59486
110 changed files with 5937 additions and 3781 deletions

View File

@@ -1,4 +1,3 @@
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';
@@ -9,57 +8,60 @@ class MockNaverApiClient extends NaverApiClient {
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);
throw const RateLimitException(
retryAfter: '60',
originalError: '429 Too Many Requests',
);
}
// 설정된 HTML이 있으면 반환
if (_htmlResponses.containsKey(url)) {
return _htmlResponses[url]!;
}
// 기본 HTML 반환
return '''
<html>
@@ -72,7 +74,7 @@ class MockNaverApiClient extends NaverApiClient {
</html>
''';
}
@override
Future<List<NaverLocalSearchResult>> searchLocal({
required String query,
@@ -85,12 +87,19 @@ class MockNaverApiClient extends NaverApiClient {
if (shouldThrowError) {
throw Exception(errorMessage);
}
if (_throw429) {
throw const RateLimitException(
retryAfter: '60',
originalError: '429 Too Many Requests',
);
}
// 설정된 검색 결과가 있으면 반환
if (_searchResults.containsKey(query)) {
return _searchResults[query] as List<NaverLocalSearchResult>;
}
// 기본 검색 결과 반환
return [
NaverLocalSearchResult.fromJson({
@@ -106,7 +115,7 @@ class MockNaverApiClient extends NaverApiClient {
}),
];
}
@override
Future<Map<String, dynamic>> fetchGraphQL({
required String operationName,
@@ -114,69 +123,71 @@ class MockNaverApiClient extends NaverApiClient {
required String query,
}) async {
if (shouldThrowError || _throw429) {
throw Exception(errorMessage);
throw const RateLimitException(
retryAfter: '60',
originalError: '429 Too Many Requests',
);
}
// 설정된 GraphQL 응답이 있으면 반환
if (_graphqlResponses.containsKey('default')) {
return {
'data': _graphqlResponses['default'],
};
return {'data': _graphqlResponses['default']};
}
// 기본 응답 반환 (places 배열 형태로 반환)
return {
'data': {
'places': [{
'id': '1',
'name': '기본 테스트 식당',
'category': '한식',
'address': '서울시 종로구',
}],
'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) {
@@ -185,39 +196,44 @@ class MockNaverApiClient extends NaverApiClient {
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');
throw const RateLimitException(
retryAfter: '60',
originalError: '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);
throw const RateLimitException(
retryAfter: '60',
originalError: '429 Too Many Requests',
);
}
// 설정된 데이터가 있으면 반환
if (_koreanTextsData.containsKey(placeId)) {
return _koreanTextsData[placeId]!;
}
// 기본 데이터 반환
return {
'success': true,
@@ -228,4 +244,4 @@ class MockNaverApiClient extends NaverApiClient {
}
}
// NaverLocalSearchResult는 naver_api_client.dart에
// NaverLocalSearchResult는 이미 naver_api_client.dart에 정의되어 있음