generated from ttipo/project-base
feat(flutter): 다이어리를 서버에 저장 (ApiDiaryRepository)
다이어리 저장소도 "장부 전체 저장" 에서 건별 계약(다이어리·기록·댓글 만들기·고치기·지우기)으로 바꿨다. - 서버 저장소: 다이어리·기록·댓글 목록을 합쳐 DiaryBook 으로 만들고, 표지·기록 사진은 저장할 때 올린다 - 기록·댓글의 작성자가 로그인한 나이면 앱의 "나"로 담는다 — 하루 한 번 쓰기 판단이 그대로 맞는다. 로그인할 때 회원 id 를 기기에 둔다 - 책장이 가득 찬 경우(DIARY_001)는 null 로 받아 화면이 직접 안내하고 전역 알림은 끈다 - 아이 삭제 때의 다이어리 정리는 서버가 하므로 dropTarget 은 아무것도 보내지 않는다 - 다이어리 화면은 실패하면 쓴 기록·댓글을 그대로 둔다 (알림은 인터셉터) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import 'dart:ui' show Color;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tma_app/core/api/file_uploader.dart';
|
||||
import 'package:tma_app/features/diary/data/api_diary_repository.dart';
|
||||
import 'package:tma_app/features/diary/domain/diary.dart';
|
||||
import 'package:tma_app/features/diary/domain/mood.dart';
|
||||
|
||||
import 'support/fake_api.dart';
|
||||
|
||||
Map<String, Object?> _diaryJson({String id = 'd1', String? coverFileId}) => {
|
||||
'id': id,
|
||||
'name': '콩이의 하루',
|
||||
'targetIds': ['p1'],
|
||||
'ribbon': '#FF7500',
|
||||
'tint': '#FF8A5B',
|
||||
'coverFileId': coverFileId,
|
||||
'mutedNotices': ['reminder'],
|
||||
'createdAt': '2026-10-11T00:00:00.000Z',
|
||||
};
|
||||
|
||||
Map<String, Object?> _postJson({String id = 'post-1', String authorId = 'me-uuid', String date = '2026-10-11'}) => {
|
||||
'id': id,
|
||||
'diaryId': 'd1',
|
||||
'authorId': authorId,
|
||||
'date': date,
|
||||
'createdAt': '2026-10-11T03:00:00.000Z',
|
||||
'text': '산책했어요',
|
||||
'mood': 'happy',
|
||||
'photoFileId': 'f1',
|
||||
};
|
||||
|
||||
void main() {
|
||||
late FakeApi api;
|
||||
late ApiDiaryRepository repo;
|
||||
String? myId = 'me-uuid';
|
||||
|
||||
setUp(() {
|
||||
api = FakeApi();
|
||||
myId = 'me-uuid';
|
||||
repo = ApiDiaryRepository(
|
||||
dio: api.dio,
|
||||
session: api.session,
|
||||
uploader: FileUploader(api.dio, api.reported.add),
|
||||
myUserId: () => myId,
|
||||
);
|
||||
});
|
||||
|
||||
test('다이어리·기록·댓글을 불러와 하나의 장부로 합친다 — 내가 쓴 기록은 "나"로 담는다', () async {
|
||||
api.on('GET', '/diaries', (r) => ok(page([_diaryJson(coverFileId: 'cover-1')])));
|
||||
api.on('GET', '/diary-posts', (r) => ok(page([_postJson(), _postJson(id: 'post-2', authorId: 'friend-uuid')])));
|
||||
api.on('GET', '/diary-comments', (r) => ok(page([
|
||||
{'id': 'c1', 'postId': 'post-1', 'authorId': 'me-uuid', 'text': '귀여워', 'createdAt': '2026-10-11T04:00:00.000Z'},
|
||||
])));
|
||||
|
||||
final book = await repo.fetch();
|
||||
|
||||
final diary = book.diaries.single;
|
||||
expect(diary.coverPath, 'server-file:cover-1');
|
||||
expect(diary.mutedNotices, {DiaryNotice.reminder});
|
||||
expect(diary.ribbon, const Color(0xFFFF7500));
|
||||
final mine = book.posts.firstWhere((p) => p.id == 'post-1');
|
||||
expect(mine.authorId, kMeAuthorId);
|
||||
expect(mine.mood, Mood.happy);
|
||||
expect(mine.photoPath, 'server-file:f1');
|
||||
expect(book.posts.firstWhere((p) => p.id == 'post-2').authorId, 'friend-uuid');
|
||||
expect(book.comments.single.authorId, kMeAuthorId);
|
||||
// 하루 한 번 쓰기 — 내가 오늘 쓴 기록을 알아본다
|
||||
expect(book.wroteOn('d1', DateTime(2026, 10, 11)), isTrue);
|
||||
});
|
||||
|
||||
test('책장이 가득 차면(DIARY_001) null 로 받고 전역 알림은 띄우지 않는다', () async {
|
||||
api.on('POST', '/diaries', (r) => failure('DIARY_001', 409));
|
||||
|
||||
final created = await repo.createDiary(
|
||||
name: '새 다이어리',
|
||||
targetIds: const [],
|
||||
ribbon: kRibbonColors.first,
|
||||
tint: kDiaryTints.first,
|
||||
);
|
||||
|
||||
expect(created, isNull);
|
||||
expect(api.reported, isEmpty);
|
||||
});
|
||||
|
||||
test('기록 남기기는 기기의 오늘 날짜와 기분·사진을 보낸다', () async {
|
||||
api.on('POST', '/diaries/:id/posts', (r) {
|
||||
expect(r.params['id'], 'd1');
|
||||
expect(r.json, {'date': '2026-10-11', 'text': '산책했어요', 'mood': 'happy', 'photoFileId': 'f1'});
|
||||
return ok(_postJson(), status: 201);
|
||||
});
|
||||
|
||||
final post = await repo.addPost(
|
||||
diaryId: 'd1',
|
||||
date: DateTime(2026, 10, 11),
|
||||
text: '산책했어요',
|
||||
mood: Mood.happy,
|
||||
photoPath: 'server-file:f1',
|
||||
);
|
||||
|
||||
expect(post.authorId, kMeAuthorId);
|
||||
expect(api.sent('POST', '/files'), isEmpty);
|
||||
});
|
||||
|
||||
test('오늘 이미 기록했으면(DIARY_002) 서버 문구 대신 사전 문구로 알린다', () async {
|
||||
api.on('POST', '/diaries/:id/posts', (r) => failure('DIARY_002', 409));
|
||||
|
||||
await expectLater(
|
||||
repo.addPost(diaryId: 'd1', date: DateTime(2026, 10, 11), text: '두 번째'),
|
||||
throwsA(anything),
|
||||
);
|
||||
expect(api.reported.single.code, 'DIARY_002');
|
||||
expect(api.reported.single.message, isNot('서버 문구'));
|
||||
});
|
||||
|
||||
test('다이어리 수정은 바탕색을 보내지 않는다 (만들 때 정한 값을 유지한다)', () async {
|
||||
api.on('PATCH', '/diaries/:id', (r) {
|
||||
expect(r.json!.containsKey('tint'), isFalse);
|
||||
expect(r.json!['mutedNotices'], ['newComment']);
|
||||
return ok(_diaryJson());
|
||||
});
|
||||
final diary = Diary(
|
||||
id: 'd1',
|
||||
name: '콩이의 하루',
|
||||
targetIds: const ['p1'],
|
||||
ribbon: kRibbonColors.first,
|
||||
tint: kDiaryTints.first,
|
||||
mutedNotices: const {DiaryNotice.newComment},
|
||||
);
|
||||
|
||||
await repo.updateDiary(diary);
|
||||
|
||||
expect(api.sent('PATCH', '/diaries/d1'), hasLength(1));
|
||||
});
|
||||
|
||||
test('아이를 지울 때 다이어리 정리는 서버가 이미 했다', () async {
|
||||
await repo.dropTarget('p1');
|
||||
|
||||
expect(api.requests, isEmpty);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user