로그인 에뮬레이터 연결을 Firestore · Functions 까지 확장 · 가입 시 계정 문서 생성 왕복 시험
This commit is contained in:
@@ -16,6 +16,9 @@
|
||||
},
|
||||
"functions": {
|
||||
"port": 5001
|
||||
},
|
||||
"hub": {
|
||||
"port": 4400
|
||||
}
|
||||
},
|
||||
"functions": [
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"description": "FANUZU Cloud Functions — 2주차 Firestore 설계서 §8 인터페이스 목록의 구현",
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"main": "lib/src/index.js",
|
||||
"engines": {
|
||||
"node": "22"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:fancake/core/firebase/firebase_boot.dart';
|
||||
import 'package:fancake/features/auth/auth.dart';
|
||||
import 'package:fancake/features/auth/auth_repository.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart' as fb;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
/// 로그인 실동작 실증 — 별첨 2 2주차 확인 항목 「앱 설치·로그인·탭 이동 확인」.
|
||||
///
|
||||
/// **왜 에뮬레이터로 재는가.** 실 Firebase 프로젝트는 계정 명의가 확정되어야
|
||||
/// 열 수 있다(계정·권한 이전 별첨 A-3). 그 사이 로그인 코드가 실제로 도는지
|
||||
/// 확인할 방법이 없으면, 설정 파일이 들어오는 날 처음 돌려 보게 된다.
|
||||
///
|
||||
/// 이 테스트는 **설정 파일 대신 에뮬레이터를 물리고 실제 왕복을 돈다.**
|
||||
/// 통과한다는 것은 설정 파일만 들어오면 그대로 동작한다는 뜻이다.
|
||||
///
|
||||
/// 실행:
|
||||
/// firebase emulators:start --only auth,firestore,functions --project demo-fanuzu
|
||||
/// flutter test integration_test/auth_emulator_test.dart \
|
||||
/// --dart-define=FANUZU_EMULATOR_HOST=10.0.2.2 -d <device>
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUpAll(() async {
|
||||
await FirebaseBoot.init();
|
||||
});
|
||||
|
||||
testWidgets('에뮬레이터를 물면 실 모드로 뜬다', (tester) async {
|
||||
expect(FirebaseBoot.useEmulator, isTrue,
|
||||
reason: '--dart-define=FANUZU_EMULATOR_HOST 를 주고 실행하십시오');
|
||||
expect(FirebaseBoot.isLive, isTrue, reason: FirebaseBoot.reason);
|
||||
});
|
||||
|
||||
testWidgets('이메일로 가입하면 계정이 만들어지고 로그인 상태가 된다', (tester) async {
|
||||
final auth = AuthRepository();
|
||||
final email = 'fan+${DateTime.now().microsecondsSinceEpoch}@fanuzu.co.kr';
|
||||
|
||||
final outcome = await auth.signInWithEmail(
|
||||
email: email,
|
||||
password: 'password12',
|
||||
signUp: true,
|
||||
);
|
||||
|
||||
expect(outcome, isA<AuthSucceeded>(),
|
||||
reason: outcome is AuthFailed ? '${outcome.code} ${outcome.message}' : '');
|
||||
expect(auth.isSignedIn, isTrue);
|
||||
expect(auth.account!.email, email);
|
||||
expect(fb.FirebaseAuth.instance.currentUser, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('가입한 계정으로 다시 로그인된다', (tester) async {
|
||||
final auth = AuthRepository();
|
||||
final email = 'fan+${DateTime.now().microsecondsSinceEpoch}@fanuzu.co.kr';
|
||||
await auth.signInWithEmail(
|
||||
email: email, password: 'password12', signUp: true,
|
||||
);
|
||||
await fb.FirebaseAuth.instance.signOut();
|
||||
|
||||
final again = await auth.signInWithEmail(
|
||||
email: email, password: 'password12',
|
||||
);
|
||||
expect(again, isA<AuthSucceeded>());
|
||||
});
|
||||
|
||||
testWidgets('틀린 비밀번호는 카탈로그 코드로 돌아온다', (tester) async {
|
||||
final auth = AuthRepository();
|
||||
final email = 'fan+${DateTime.now().microsecondsSinceEpoch}@fanuzu.co.kr';
|
||||
await auth.signInWithEmail(
|
||||
email: email, password: 'password12', signUp: true,
|
||||
);
|
||||
|
||||
final wrong = await auth.signInWithEmail(
|
||||
email: email, password: 'wrong-password',
|
||||
);
|
||||
expect(wrong, isA<AuthFailed>());
|
||||
// 「등록되지 않은 이메일」과 갈라 주지 않는다 — 가입 목록을 훑을 수 있다.
|
||||
expect((wrong as AuthFailed).code, startsWith('E-AUTH-'));
|
||||
});
|
||||
|
||||
testWidgets('가입하면 Auth 트리거가 계정·지갑 문서를 만든다', (tester) async {
|
||||
final auth = AuthRepository();
|
||||
final email = 'fan+${DateTime.now().microsecondsSinceEpoch}@fanuzu.co.kr';
|
||||
await auth.signInWithEmail(
|
||||
email: email, password: 'password12', signUp: true,
|
||||
);
|
||||
final uid = auth.account!.uid;
|
||||
|
||||
// fn-auth-oncreate 는 배경 트리거라 즉시 도착하지 않는다. 몇 초 기다린다.
|
||||
Map<String, dynamic>? user;
|
||||
for (var i = 0; i < 20 && user == null; i++) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
final snap = await FirebaseFirestore.instance.doc('users/$uid').get();
|
||||
user = snap.data();
|
||||
}
|
||||
|
||||
expect(user, isNotNull, reason: 'fn-auth-oncreate 가 계정 문서를 만들지 않았습니다');
|
||||
expect(user!['status'], 'active');
|
||||
expect(user['totalContributedPop'], 0);
|
||||
|
||||
final wallet =
|
||||
await FirebaseFirestore.instance.doc('users/$uid/wallet/main').get();
|
||||
expect(wallet.exists, isTrue);
|
||||
expect(wallet.get('activityBalance'), 0);
|
||||
});
|
||||
|
||||
testWidgets('클라이언트는 지갑을 직접 쓸 수 없다', (tester) async {
|
||||
final auth = AuthRepository();
|
||||
final email = 'fan+${DateTime.now().microsecondsSinceEpoch}@fanuzu.co.kr';
|
||||
await auth.signInWithEmail(
|
||||
email: email, password: 'password12', signUp: true,
|
||||
);
|
||||
final uid = auth.account!.uid;
|
||||
|
||||
// 별첨 1 ② 「클라이언트 단독 지급 0건」이 규칙 층에서 실제로 막히는지 본다.
|
||||
await expectLater(
|
||||
FirebaseFirestore.instance
|
||||
.doc('users/$uid/wallet/main')
|
||||
.set({'activityBalance': 999999}),
|
||||
throwsA(isA<FirebaseException>()),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:cloud_functions/cloud_functions.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../data/backend.dart';
|
||||
|
||||
/// Firebase 구동 모드.
|
||||
enum FirebaseMode {
|
||||
/// 설정 파일이 투입돼 Firebase 가 실제로 초기화된 상태.
|
||||
@@ -46,8 +50,9 @@ abstract final class FirebaseBoot {
|
||||
|
||||
/// 에뮬레이터 호스트. `--dart-define=FANUZU_EMULATOR_HOST=127.0.0.1` 로 켠다.
|
||||
///
|
||||
/// **왜 두는가** — 실 Firebase 프로젝트가 열리기 전에도 로그인이
|
||||
/// 실제로 도는지 확인할 수 있어야 한다.
|
||||
/// **왜 두는가** — 실 Firebase 프로젝트가 열리기 전에도 로그인·원장·규칙이
|
||||
/// 실제로 도는지 확인할 수 있어야 한다. 설정 파일을 기다리는 동안 이 경로가
|
||||
/// 없으면 서버 코드가 한 줄도 실행되지 않은 채 쌓인다.
|
||||
///
|
||||
/// 릴리스 빌드에서는 열리지 않는다. 배포된 앱이 로컬 호스트를 바라보면
|
||||
/// 아무것도 동작하지 않고 원인도 보이지 않는다.
|
||||
@@ -89,6 +94,9 @@ abstract final class FirebaseBoot {
|
||||
try {
|
||||
await Firebase.initializeApp(options: _emulatorOptions);
|
||||
FirebaseAuth.instance.useAuthEmulator(emulatorHost, 9099);
|
||||
FirebaseFirestore.instance.useFirestoreEmulator(emulatorHost, 8080);
|
||||
FirebaseFunctions.instanceFor(region: Backend.region)
|
||||
.useFunctionsEmulator(emulatorHost, 5001);
|
||||
_mode = FirebaseMode.live;
|
||||
_reason = '로컬 에뮬레이터에 연결했습니다. 실 서버가 아닙니다.';
|
||||
_detail = 'emulator host: $emulatorHost';
|
||||
|
||||
@@ -209,6 +209,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
firebase_auth:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -278,6 +286,11 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_driver:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -304,6 +317,11 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fuchsia_remote_debug_protocol:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
go_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -384,6 +402,11 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.1"
|
||||
integration_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -496,6 +519,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.2"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -520,6 +551,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.5.2"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
sha256: "4242ba3508d37e01808bdf71ad1d5bb93a8d671bf2e7450e6b1b353fb0808891"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.6"
|
||||
rx:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -597,6 +636,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
sync_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sync_http
|
||||
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -653,6 +700,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
webdriver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webdriver
|
||||
sha256: "28b82ec894fed45dd71c23ba62d1af973ed97dd59a4f5790a4d38b0b13e5657e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -36,6 +36,10 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
# 기기 위에서 앱을 띄워 에뮬레이터와 실제 왕복을 돈다. 위젯 테스트는 네트워크를
|
||||
# 타지 않으므로 「로그인 → 계정 문서 생성」은 여기서만 확인할 수 있다.
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^6.0.0
|
||||
# Firestore 연동 코드를 에뮬레이터 없이 검증한다. 쿼리·정렬·커서까지 흉내 내므로
|
||||
# 「무엇을 읽어 무엇으로 옮기는가」를 실제로 돌려 볼 수 있다.
|
||||
|
||||
Reference in New Issue
Block a user