108 lines
4.9 KiB
Dart
108 lines
4.9 KiB
Dart
import 'package:fancake/core/data/backend.dart';
|
|
import 'package:fancake/core/data/repositories.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:fancake/features/quest/quest_data.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:integration_test/integration_test.dart';
|
|
|
|
/// 퀘스트 참여 실왕복 — 별첨 1 ② 「클라이언트 단독 지급 0건」과 ③ 「서버검증 경유」.
|
|
///
|
|
/// **앱에서 Cloud Functions 를 직접 부른다.** 앞선 `pop_loop_emulator_test.dart` 는
|
|
/// 서버가 쓴 값을 앱이 읽는 쪽만 봤고, 여기서는 **앱 → Callable → 원장 → 앱** 한 바퀴를
|
|
/// 돈다. 로그인도 실제 Auth 에뮬레이터를 지난다.
|
|
///
|
|
/// 실행 — 에뮬레이터 세 개를 함께 띄운다. **저장소 안의 firebase-tools** 를 쓴다.
|
|
///
|
|
/// ```bash
|
|
/// adb reverse tcp:8080 tcp:8080 && adb reverse tcp:9099 tcp:9099 \
|
|
/// && adb reverse tcp:5001 tcp:5001
|
|
/// cd firebase
|
|
/// JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home \
|
|
/// test/node_modules/.bin/firebase emulators:exec \
|
|
/// --only auth,firestore,functions --project demo-fanuzu \
|
|
/// "bash ../tool/emulator_quest_roundtrip.sh"
|
|
/// ```
|
|
///
|
|
/// 시드(퀘스트 정의·운영값)는 그 스크립트가 에뮬레이터 REST 로 넣는다 — 보안 규칙이
|
|
/// `quests` 쓰기를 마스터로 막아 두어 앱이 만들 수 없다(그게 맞다).
|
|
void main() {
|
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
/// 시드 스크립트가 넣는 퀘스트 문서 ID.
|
|
const questId = 'daily-checkin';
|
|
|
|
late AuthRepository auth;
|
|
late Repositories repositories;
|
|
|
|
setUpAll(() async {
|
|
await FirebaseBoot.init();
|
|
auth = AuthRepository();
|
|
// 매 회차 새 계정이다. `fn-auth-oncreate` 트리거가 계정·지갑 문서를 만드는 것까지
|
|
// 이 왕복에 들어간다 — 앱은 `users/{uid}` 를 직접 만들지 못한다(보안 규칙).
|
|
final email = 'quest+${DateTime.now().microsecondsSinceEpoch}@fanuzu.co.kr';
|
|
final outcome = await auth.signInWithEmail(
|
|
email: email,
|
|
password: 'password12',
|
|
signUp: true,
|
|
);
|
|
expect(outcome, isA<AuthSucceeded>(), reason: 'Auth 에뮬레이터에 붙지 못했습니다');
|
|
repositories = Repositories(live: true);
|
|
});
|
|
|
|
testWidgets('에뮬레이터를 물고 로그인까지 지난다', (tester) async {
|
|
expect(FirebaseBoot.useEmulator, isTrue,
|
|
reason: '--dart-define=FANUZU_EMULATOR_HOST 를 주고 실행하십시오');
|
|
expect(FirebaseBoot.isLive, isTrue, reason: FirebaseBoot.reason);
|
|
expect(auth.isSignedIn, isTrue);
|
|
});
|
|
|
|
testWidgets('출석 참여 한 번이 서버 지급과 원장까지 간다', (tester) async {
|
|
final quests = await repositories.quest.load();
|
|
final attend = quests.firstWhere((card) => card.id == questId);
|
|
expect(attend.type, QuestType.attend);
|
|
expect(attend.stage, QuestStage.notStarted);
|
|
|
|
// 앱이 부르는 것은 Firestore 쓰기가 아니라 Callable 이다.
|
|
final reward = await repositories.quest.verify(attend);
|
|
expect(reward.rewardedPop, 10);
|
|
expect(reward.balanceAfter, 10);
|
|
expect(reward.txId, isNotNull);
|
|
|
|
// 원장 한 행 — 앱이 쓴 것이 아니라 서버가 쓴 것이다.
|
|
final uid = repositories.backend.requireUid;
|
|
final ledger =
|
|
await repositories.backend.db.collection('users/$uid/popTransactions').get();
|
|
expect(ledger.size, 1);
|
|
expect(ledger.docs.single.data()['reasonCode'], 'QUEST_ATTEND');
|
|
expect(ledger.docs.single.data()['serverVerified'], isTrue);
|
|
});
|
|
|
|
testWidgets('보유 POP 과 퀘스트 상태가 서버 값으로 다시 그려진다', (tester) async {
|
|
// HOME 은 지갑을 읽는다. 출석 보상 10 POP 이 그대로 보인다.
|
|
final home = await repositories.home.load();
|
|
expect(home.popBalance, 10);
|
|
|
|
// 목록을 다시 읽으면 서버 진행 문서로 「보상완료」다 — 앱이 기억한 값이 아니다.
|
|
final again = await repositories.quest.load();
|
|
expect(again.firstWhere((card) => card.id == questId).stage, QuestStage.rewarded);
|
|
});
|
|
|
|
testWidgets('같은 날 두 번째 참여는 서버가 거부한다', (tester) async {
|
|
final quests = await repositories.quest.load();
|
|
final attend = quests.firstWhere((card) => card.id == questId);
|
|
|
|
await expectLater(
|
|
repositories.quest.verify(attend),
|
|
// 화면은 gRPC 상태가 아니라 에러 카탈로그 코드로 분기한다.
|
|
throwsA(isA<BackendException>().having((e) => e.code, 'code', 'E-POP-03')),
|
|
);
|
|
|
|
final uid = repositories.backend.requireUid;
|
|
final ledger =
|
|
await repositories.backend.db.collection('users/$uid/popTransactions').get();
|
|
expect(ledger.size, 1, reason: '거부된 호출이 원장을 늘리면 안 된다');
|
|
});
|
|
}
|