걸음 수 Health Connect·HealthKit · 위치 퀘스트 사진·QR 인증 · 행성 형태 확정

This commit is contained in:
Sn-Kinos
2026-10-05 23:57:01 +09:00
parent dc9861df38
commit dce2f64362
44 changed files with 930 additions and 66 deletions
+4 -1
View File
@@ -532,7 +532,10 @@ class _AdRepo extends SampleQuestRepository {
var verified = 0;
@override
Future<QuestReward> verify(QuestCard card) async {
Future<QuestReward> verify(
QuestCard card, {
PlaceProof proof = const PlaceProof(),
}) async {
verified += 1;
if (verifyError case final error?) throw error;
return super.verify(card);
+9
View File
@@ -1,5 +1,6 @@
import 'package:fancake/features/home/home_data.dart';
import 'package:fancake/features/home/home_repository_firestore.dart' show kstWeekStart;
import 'package:fancake/features/quest/quest_evidence.dart' show kstMidnight;
import 'package:flutter_test/flutter_test.dart';
/// HOME 주간 정보(A-37 · HOME 정보위계 v1.0 §2 · §3).
@@ -50,6 +51,14 @@ void main() {
expect(none.rank, isNull);
});
test('만보기 Health 집계 시작은 그 날 0시(KST) — 서버 dayKey 와 같은 경계 (B-02)', () {
// 10-07 00:30 KST(= 10-06 15:30 UTC) → 10-07 00:00 KST = 10-06 15:00 UTC.
expect(
kstMidnight(DateTime.parse('2026-10-06T15:30:00Z')),
DateTime.parse('2026-10-06T15:00:00Z'),
);
});
test('이번 주 시작은 KST 월요일 00:00', () {
// 2026-10-07(수) 15:00 KST → 2026-10-05(월) 00:00 KST = 10-04 15:00 UTC.
expect(
+1 -2
View File
@@ -18,10 +18,9 @@ void main() {
});
test('생성 때 고른 형태가 HOME · GALAXY · 상세 · MY 그림을 정한다 (A-08 · Figma 344:214421)', () {
for (var i = 1; i <= 5; i++) {
for (var i = 1; i <= 6; i++) {
expect(planetShapeImage('shape$i'), 'assets/figma/planet-shape-$i.png');
}
expect(planetShapeImage('shape6'), 'assets/figma/planet-shape-placeholder.png');
expect(planetShapeImage(null), isNull, reason: '성운 · 옛 행성은 화면이 준 그림');
expect(planetShapeImage('shape7'), isNull);
// 생성 화면이 보내는 키와 같은 표여야 한다.
+37
View File
@@ -230,6 +230,20 @@ service cloud.firestore {
expect(backend.calls.single.payload['occurredAt'], isA<String>());
});
test('Health 가 있으면 그 날 0시부터의 합계를 센서보다 먼저 쓴다 (B-02)', () async {
final db = await _seed();
final backend = _RecordingBackend(db);
final repo = FirestoreQuestRepository(
backend,
todaySteps: () async => 7_300,
stepCount: () => Stream.fromIterable([40_000]),
);
final card = _card(await repo.load(), 'steps');
expect(await repo.progressStream(card).first, 7_300);
await repo.verify(card);
expect(backend.calls.single.payload['steps'], 7_300);
});
test('앱을 껐다 켜도 오늘치가 0 으로 돌아가지 않는다', () async {
final db = await _seed();
// 첫 실행 — 기준선 40,000 을 잡고 5,000 걷는다.
@@ -300,6 +314,29 @@ service cloud.firestore {
expect(call.payload['mocked'], isTrue);
});
test('위치 Quest 의 인증 방법을 상세에 싣고 QR 은 GPS 와 함께 보낸다 (C-03 · PRD-05)', () async {
final db = await _seed();
final backend = _RecordingBackend(db);
final repo = FirestoreQuestRepository(
backend,
placeFix: () async =>
const PlaceFix(lat: 37.5, lng: 126.9, accuracyM: 5, mocked: false),
);
// 수단을 적지 않은 운영 Quest 는 GPS 하나.
expect((await repo.detail('place')).verifyMethods, ['gps']);
await db.doc('quests/place').update({'verifyMethods': ['gps', 'qr']});
await db.doc('questSecrets/place').set({'authorUid': 'other', 'qrCode': 'secret'});
final detail = await repo.detail('place');
expect(detail.verifyMethods, ['gps', 'qr']);
expect(detail.qrCode, isNull, reason: '참여자에게는 QR 값을 주지 않는다');
await repo.verify(detail.card, proof: const PlaceProof(qr: 'scanned'));
final call = backend.calls.single;
expect(call.payload['qr'], 'scanned');
expect(call.payload['lat'], 37.5);
expect(call.payload.containsKey('photo'), isFalse);
});
test('장소가 등록되지 않은 위치 퀘스트는 서버를 부르지 않는다', () async {
final db = await _seed();
await db.doc('quests/place').set({