2026-09-19 00:40:02 +08:00
|
|
|
|
/// 照片域模型往返测试(P3 计划 Task 2)。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// fixture 来源(字段逐一转写):
|
|
|
|
|
|
/// - openapi.yaml components.schemas.PhotoRecord / AdminPhoto /
|
|
|
|
|
|
/// AdminPhotoUploader / PhotoVisibility / PhotoProcessingState;
|
|
|
|
|
|
/// - services/api/test/photos-admin.e2e-spec.ts 的 ADMIN_PHOTO_KEYS
|
|
|
|
|
|
/// (PhotoRecord 必返字段 + uploader 内嵌)与种子形态
|
|
|
|
|
|
/// (visibility=published / processingState=ready、categoryId 可空、
|
|
|
|
|
|
/// displayName 取资料字段而非账号)。
|
|
|
|
|
|
library;
|
|
|
|
|
|
|
|
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
|
|
import 'package:photo_live/features/photos/models.dart';
|
|
|
|
|
|
|
|
|
|
|
|
final Map<String, dynamic> photoRecordFixture = <String, dynamic>{
|
|
|
|
|
|
'photoId': 'pa-photo-p1',
|
|
|
|
|
|
'albumId': 'cktragt000001',
|
|
|
|
|
|
'categoryId': 'ckucat0000001',
|
|
|
|
|
|
'uploaderId': 'usr-col-02',
|
|
|
|
|
|
'visibility': 'published',
|
|
|
|
|
|
'processingState': 'ready',
|
|
|
|
|
|
'bytes': 4096000,
|
|
|
|
|
|
'width': 4032,
|
|
|
|
|
|
'height': 3024,
|
|
|
|
|
|
'shotAt': '2026-09-12T07:55:00.000Z',
|
|
|
|
|
|
'uploadedAt': '2026-09-12T08:05:00.000Z',
|
|
|
|
|
|
'publishedAt': '2026-09-12T08:06:00.000Z',
|
|
|
|
|
|
'likeCount': 3,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
|
test('PhotoRecord fromJson/toJson 往返(管理端全字段)', () {
|
|
|
|
|
|
final model = PhotoRecord.fromJson(photoRecordFixture);
|
|
|
|
|
|
expect(model.visibility, PhotoVisibility.published);
|
|
|
|
|
|
expect(model.processingState, PhotoProcessingState.ready);
|
|
|
|
|
|
expect(model.likeCount, 3);
|
|
|
|
|
|
expect(model.toJson(), equals(photoRecordFixture));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('PhotoRecord 可空字段:处理前宽高为 null、未发布为 null', () {
|
|
|
|
|
|
final fixture = {
|
|
|
|
|
|
...photoRecordFixture,
|
|
|
|
|
|
'categoryId': null,
|
|
|
|
|
|
'width': null,
|
|
|
|
|
|
'height': null,
|
|
|
|
|
|
'shotAt': null,
|
|
|
|
|
|
'publishedAt': null,
|
|
|
|
|
|
'visibility': 'unpublished',
|
|
|
|
|
|
'processingState': 'uploaded',
|
|
|
|
|
|
};
|
|
|
|
|
|
final model = PhotoRecord.fromJson(fixture);
|
|
|
|
|
|
expect(model.categoryId, isNull);
|
|
|
|
|
|
expect(model.width, isNull);
|
|
|
|
|
|
expect(model.height, isNull);
|
|
|
|
|
|
expect(model.shotAt, isNull);
|
|
|
|
|
|
expect(model.publishedAt, isNull);
|
|
|
|
|
|
expect(model.visibility, PhotoVisibility.unpublished);
|
|
|
|
|
|
expect(model.processingState, PhotoProcessingState.uploaded);
|
|
|
|
|
|
expect(model.toJson(), equals(fixture));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('PhotoRecord 解析毫秒精度时间(服务端 Date.toISOString 形态)', () {
|
|
|
|
|
|
final model = PhotoRecord.fromJson({
|
|
|
|
|
|
...photoRecordFixture,
|
|
|
|
|
|
'uploadedAt': '2026-09-12T08:05:00.000Z',
|
|
|
|
|
|
});
|
|
|
|
|
|
expect(model.uploadedAt, DateTime.utc(2026, 9, 12, 8, 5));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('AdminPhoto fromJson/toJson 往返(PhotoRecord + uploader 内嵌)', () {
|
|
|
|
|
|
final fixture = {
|
|
|
|
|
|
...photoRecordFixture,
|
|
|
|
|
|
'uploader': {
|
|
|
|
|
|
'userId': 'usr-col-02',
|
|
|
|
|
|
'account': 'collab01',
|
|
|
|
|
|
'displayName': '李四',
|
|
|
|
|
|
},
|
2026-09-21 14:09:35 +08:00
|
|
|
|
'preview': null,
|
2026-09-19 00:40:02 +08:00
|
|
|
|
};
|
|
|
|
|
|
final model = AdminPhoto.fromJson(fixture);
|
|
|
|
|
|
// e2e:uploader 映射取的是资料 displayName,而非账号。
|
|
|
|
|
|
expect(model.uploader.displayName, '李四');
|
|
|
|
|
|
expect(model.uploader.userId, model.uploaderId);
|
2026-09-21 14:09:35 +08:00
|
|
|
|
expect(model.preview, isNull);
|
2026-09-19 00:40:02 +08:00
|
|
|
|
expect(model.toJson(), equals(fixture));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-09-21 14:09:35 +08:00
|
|
|
|
test('AdminPhoto preview(UI-01):非空往返与缺键容错', () {
|
|
|
|
|
|
final withPreview = {
|
|
|
|
|
|
...photoRecordFixture,
|
|
|
|
|
|
'uploader': {
|
|
|
|
|
|
'userId': 'usr-col-02',
|
|
|
|
|
|
'account': 'collab01',
|
|
|
|
|
|
'displayName': '李四',
|
|
|
|
|
|
},
|
|
|
|
|
|
'preview': {
|
|
|
|
|
|
'thumb': {
|
|
|
|
|
|
'url': 'http://minio:9000/bucket/variants/thumb/p1.webp?X-Amz-Signature=t',
|
|
|
|
|
|
'expiresAt': '2026-09-21T09:00:00.000Z',
|
|
|
|
|
|
},
|
|
|
|
|
|
'browse': {
|
|
|
|
|
|
'url': 'http://minio:9000/bucket/variants/browse/p1.webp?X-Amz-Signature=b',
|
|
|
|
|
|
'expiresAt': '2026-09-21T09:00:00.000Z',
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
final model = AdminPhoto.fromJson(withPreview);
|
|
|
|
|
|
expect(model.preview, isNotNull);
|
|
|
|
|
|
expect(model.preview!.thumb.url, contains('/thumb/'));
|
|
|
|
|
|
expect(model.preview!.browse.expiresAt, DateTime.utc(2026, 9, 21, 9));
|
|
|
|
|
|
expect(model.toJson(), equals(withPreview));
|
|
|
|
|
|
|
|
|
|
|
|
// 旧响应缺 preview 键 → null(旧响应不 break)。
|
|
|
|
|
|
final legacy = {...withPreview}..remove('preview');
|
|
|
|
|
|
expect(AdminPhoto.fromJson(legacy).preview, isNull);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-09-19 00:40:02 +08:00
|
|
|
|
test('PhotoVisibility / PhotoProcessingState 全枚举解析', () {
|
|
|
|
|
|
for (final value in const [
|
|
|
|
|
|
'unpublished',
|
|
|
|
|
|
'published',
|
|
|
|
|
|
'hidden',
|
|
|
|
|
|
'deleted',
|
|
|
|
|
|
]) {
|
|
|
|
|
|
expect(PhotoVisibility.fromJson(value).wire, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (final value in const [
|
|
|
|
|
|
'pending_upload',
|
|
|
|
|
|
'uploaded',
|
|
|
|
|
|
'processing',
|
|
|
|
|
|
'ready',
|
|
|
|
|
|
'processing_failed',
|
|
|
|
|
|
]) {
|
|
|
|
|
|
expect(PhotoProcessingState.fromJson(value).wire, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
expect(() => PhotoVisibility.fromJson('archived'), throwsFormatException);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
() => PhotoProcessingState.fromJson('failed'),
|
|
|
|
|
|
throwsFormatException,
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|