Files
patbond-flutter/test/helpers/pet_test_helpers.dart
T
lixi 5b34fa336b 新增:体重录入与历史列表接入真实数据(T2-13 体重半边)
- WeightRecordsPage:cursor 分页(加载更多/末页收起/翻页失败保留重试)、
  loading/empty/error/retry 四态、下拉刷新;viewer 隐藏录入入口
- WeightFormPage:weightKg 契约区间 (0,500] 与两位小数前端校验 + 40000
  兜底;称重时间转 UTC 上送;错误三层分层沿用登录纵切
- health_record 域埋点强类型封装(06 §1.4:create_started/succeeded/
  failed + viewed,httpStatus 由业务码推导;viewed 按工单口径取列表曝光)
- health_record_display 纯函数(体重解析/展示、疫苗状态映射、42201
  规则前端拦截函数);PetsController 暴露 repository(22 号 §7 交接)
- 测试 177 → 205(+28)全绿

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 13:17:17 +08:00

259 lines
7.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// pets 域测试样本(契约 openapi.yaml v1.2.0 各 schema 全字段 JSON
/// 与共享假仓库。
library;
import 'package:patbond_flutter/features/pets/pet_models.dart';
import 'package:patbond_flutter/features/pets/pets_repository.dart';
Map<String, Object?> okListEnvelope(List<Object?> data) => {
'code': 0,
'message': 'ok',
'data': data,
};
Map<String, dynamic> samplePetJson({
Map<String, Object?> overrides = const {},
}) => {
'id': 'p-1',
'name': '豆豆',
'species': 'dog',
'breedId': 'b-1',
'breedDisplayName': '柴犬',
'customBreedName': null,
'sex': 'male',
'birthDate': '2024-03-15',
'birthDateEstimated': false,
'personality': null,
'microchipNo': null,
'sterilizedOn': null,
'status': 'active',
'myRole': 'owner',
'createdAt': '2026-09-01T10:00:00+08:00',
'updatedAt': '2026-09-02T10:00:00+08:00',
'version': 3,
...overrides,
};
Map<String, dynamic> sampleWeightJson() => {
'id': 'w-1',
'petId': 'p-1',
'weightKg': 4.35,
'measuredAt': '2026-09-07T09:00:00+08:00',
'source': 'manual',
'note': null,
'createdAt': '2026-09-07T09:01:00+08:00',
};
Map<String, dynamic> sampleVaccinationJson() => {
'id': 'vx-1',
'petId': 'p-1',
'vaccineId': 'v-1',
'vaccineName': '狂犬疫苗',
'seriesKey': 'rabies-initial',
'doseNo': 1,
'doseLabel': null,
'status': 'scheduled',
'plannedOn': '2026-10-01',
'administeredOn': null,
'nextDueOn': null,
'manufacturer': null,
'batchNo': null,
'notes': null,
'createdAt': '2026-09-01T10:00:00+08:00',
'updatedAt': '2026-09-01T10:00:00+08:00',
'version': 1,
};
Map<String, dynamic> sampleHealthEventJson() => {
'id': 'e-1',
'petId': 'p-1',
'eventType': 'medical',
'occurredAt': '2026-09-05T14:00:00+08:00',
'title': '皮肤检查',
'notes': null,
'amountCents': 12850,
'createdByUserId': 'u-1',
'createdAt': '2026-09-05T14:05:00+08:00',
'updatedAt': '2026-09-05T14:05:00+08:00',
'version': 1,
};
Map<String, dynamic> sampleCareReminderJson() => {
'id': 'r-1',
'petId': 'p-1',
'reminderType': 'checkup',
'title': '年度体检',
'dueAt': '2026-10-01T00:00:00+08:00',
'status': 'pending',
'completedAt': null,
'createdAt': '2026-09-01T10:00:00+08:00',
'updatedAt': '2026-09-01T10:00:00+08:00',
};
Map<String, dynamic> samplePetSummaryJson() => {
'petId': 'p-1',
'latestWeight': {'weightKg': 4.35, 'measuredAt': '2026-09-07T09:00:00+08:00'},
'vaccinationProgress': {'completedDoses': 2, 'totalDoses': 3},
'nextVaccination': {
'vaccinationId': 'vx-3',
'vaccineId': 'v-1',
'vaccineName': '狂犬疫苗',
'doseNo': 3,
'doseLabel': null,
'dueOn': '2026-08-01',
'source': 'planned',
},
'monthlyExpense': {
'month': '2026-09',
'timezone': 'Asia/Shanghai',
'amountCents': 12850,
},
};
Map<String, dynamic> sampleBreedJson({
String id = 'b-1',
String species = 'dog',
String code = 'shiba',
String displayName = '柴犬',
}) => {'id': id, 'species': species, 'code': code, 'displayName': displayName};
/// 快速构造 Petwidget/controller 测试共用)。
Pet buildPet(
String id, {
String name = '豆豆',
int version = 1,
Map<String, Object?> overrides = const {},
}) => Pet.fromJson(
samplePetJson(
overrides: {'id': id, 'name': name, 'version': version, ...overrides},
),
);
Map<String, dynamic> sampleVaccineCatalogJson({
String id = 'v-1',
String code = 'rabies',
String name = '狂犬疫苗',
String species = 'dog',
}) => {
'id': id,
'code': code,
'name': name,
'species': species,
'description': null,
};
/// 快速构造体重记录(widget 测试共用)。
WeightRecord buildWeight(
String id, {
double weightKg = 4.35,
String measuredAt = '2026-09-07T09:00:00+08:00',
Map<String, Object?> overrides = const {},
}) => WeightRecord.fromJson({
...sampleWeightJson(),
'id': id,
'weightKg': weightKg,
'measuredAt': measuredAt,
...overrides,
});
/// 快速构造疫苗记录。
Vaccination buildVaccination(
String id, {
Map<String, Object?> overrides = const {},
}) =>
Vaccination.fromJson({...sampleVaccinationJson(), 'id': id, ...overrides});
/// 快速构造摘要(缺省为「三聚合齐备」样本;overrides 可置 null 验证空态)。
PetSummary buildSummary({Map<String, Object?> overrides = const {}}) =>
PetSummary.fromJson({...samplePetSummaryJson(), ...overrides});
/// 假仓库:各方法可注入行为;listPets 默认空列表,其余未注入的方法抛
/// UnimplementedError22 号报告 §7widget 测试注入假仓库的共享实现)。
class FakePetsRepository implements PetsRepository {
Future<List<Pet>> Function()? listPetsHandler;
Future<Pet> Function(CreatePetRequest)? createPetHandler;
Future<Pet> Function(String)? getPetHandler;
Future<Pet> Function(String, UpdatePetRequest)? updatePetHandler;
Future<List<Breed>> Function(PetSpecies?)? listBreedsHandler;
Future<CursorPage<WeightRecord>> Function(String, int?, String?)?
listWeightsHandler;
Future<WeightRecord> Function(String, CreateWeightRequest)?
createWeightHandler;
Future<List<VaccineCatalogItem>> Function(PetSpecies?)?
listVaccineCatalogHandler;
Future<List<Vaccination>> Function(String)? listVaccinationsHandler;
Future<Vaccination> Function(String, CreateVaccinationRequest)?
createVaccinationHandler;
Future<PetSummary> Function(String, String?)? getPetSummaryHandler;
@override
Future<List<Pet>> listPets() =>
listPetsHandler?.call() ?? Future.value(const []);
@override
Future<Pet> createPet(CreatePetRequest request) => createPetHandler!(request);
@override
Future<Pet> getPet(String petId) => getPetHandler!(petId);
@override
Future<Pet> updatePet(String petId, UpdatePetRequest request) =>
updatePetHandler!(petId, request);
@override
Future<List<Breed>> listBreeds({PetSpecies? species}) =>
listBreedsHandler?.call(species) ??
Future.value([Breed.fromJson(sampleBreedJson())]);
@override
Future<CursorPage<WeightRecord>> listWeights(
String petId, {
int? limit,
String? cursor,
}) => listWeightsHandler!(petId, limit, cursor);
@override
Future<WeightRecord> createWeight(
String petId,
CreateWeightRequest request,
) => createWeightHandler!(petId, request);
@override
Future<List<VaccineCatalogItem>> listVaccineCatalog({PetSpecies? species}) =>
listVaccineCatalogHandler?.call(species) ??
Future.value([VaccineCatalogItem.fromJson(sampleVaccineCatalogJson())]);
@override
Future<List<Vaccination>> listVaccinations(String petId) =>
listVaccinationsHandler!(petId);
@override
Future<Vaccination> createVaccination(
String petId,
CreateVaccinationRequest request,
) => createVaccinationHandler!(petId, request);
@override
Future<PetSummary> getPetSummary(String petId, {String? tz}) =>
getPetSummaryHandler?.call(petId, tz) ??
// 缺省给「无任何记录」空摘要(契约 null 语义),既有详情页测试
// 不必逐个注入。
Future.value(
PetSummary.fromJson({
'petId': petId,
'latestWeight': null,
'vaccinationProgress': null,
'nextVaccination': null,
'monthlyExpense': {
'month': '2026-09',
'timezone': 'UTC',
'amountCents': 0,
},
}),
);
@override
dynamic noSuchMethod(Invocation invocation) =>
throw UnimplementedError('${invocation.memberName}');
}