feat(app): 设置页 UI + Mood/成就/贴纸 BLoC 接入 API + B7 测试扩展

前端改动:
- 新建设置页面 (主题切换/关于/隐私政策/用户协议/儿童隐私保护)
- SettingsBloc 注册到 MultiRepositoryProvider 全局可访问
- MoodBloc 修复编译错误 + 接入 /diary/stats/mood API
- MoodPage 添加错误状态展示和重试按钮
- AchievementBloc + 页面改造接入 /diary/achievements API
- StickerBloc + 页面改造接入 /diary/sticker-packs API
- TemplateBloc + 页面改造接入 /diary/templates API
- ProfilePage 设置入口改为跳转 /settings
- 添加 /settings 路由

后端改动:
- 扩展 mood_stats_service 测试 (连续天数算法/心情计数/边界场景)
- 新增 class_service 测试 (班级码生成/唯一性/错误映射)
- 新增 achievement_service 测试 (DTO 结构/序列化/map 构建)
- 新增 sticker_service 测试 (DTO 序列化/错误处理)
- 扩展 dto.rs 测试 (achievement/mood_stats/sticker/template/notification)
- 清理 2 个 unused import warning

验证:
- cargo check 0 error 0 warning
- flutter analyze 0 error
This commit is contained in:
iven
2026-06-01 11:19:43 +08:00
parent 860e9e5d22
commit 8331db63ba
19 changed files with 1749 additions and 326 deletions

View File

@@ -298,3 +298,56 @@ fn member_model_to_resp(model: class_member::Model) -> ClassMemberResp {
joined_at: model.joined_at,
}
}
#[cfg(test)]
mod tests {
use super::*;
// ===== 班级码生成测试 =====
#[test]
fn generate_class_code_is_6_chars() {
let code = generate_class_code();
assert_eq!(code.len(), 6, "班级码必须是 6 位");
}
#[test]
fn generate_class_code_is_alphanumeric() {
let code = generate_class_code();
assert!(
code.chars().all(|c| c.is_ascii_alphanumeric()),
"班级码必须全部是字母数字"
);
}
#[test]
fn generate_class_code_is_unique() {
let codes: std::collections::HashSet<String> = (0..100)
.map(|_| generate_class_code())
.collect();
// 100 个码应该全部不同(概率上几乎确定)
assert!(codes.len() > 90, "生成的班级码应该高度唯一");
}
// ===== 错误映射测试 =====
#[test]
fn invalid_class_code_error() {
let err = DiaryError::InvalidClassCode;
assert!(err.to_string().contains("无效"));
}
#[test]
fn class_code_expired_error() {
let err = DiaryError::ClassCodeExpired;
assert!(err.to_string().contains("过期"));
}
#[test]
fn class_code_locked_error() {
let err = DiaryError::ClassCodeLocked {
lockout_minutes: 30,
};
assert!(err.to_string().contains("30"));
}
}