feat(app): wire server dedup - force passthrough + PHOTO_DUPLICATE classification & l10n
Task 8: close the gap between server-side duplicate detection (90dcc58)
and the app.
- ApiClient.mapDioException: normalize 409 wire body reusing
VALIDATION_FAILED with message=PHOTO_DUPLICATE to
ErrorCodes.photoDuplicate (message-based; auto-compat if the contract
later adds a dedicated code)
- l10n zh/en errorPhotoDuplicate; error_l10n maps the code (queue page
inherits via fallback)
- UploadEngine._classifyFailure: PHOTO_DUPLICATE joins the no-auto-retry
bucket (retrying the same init body would 409 again); force to skip
dedup travels only through the enqueue path
- UploadTasks drift table +force column (schema v2, addColumn migration;
legacy rows default false); UploadTaskDraft/UploadTask/prepare carry it
- enqueueForUpload +forcePaths; duplicate dialog "全部上传" (capture page
+ album detail page) now returns DuplicateDialogChoice so confirmed
duplicates enqueue with force=true (server adjudicates owner/admin)
- engine init body carries force only when true (absent otherwise)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,50 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('mapDioException:P0-1 服务端查重 409 归一(Task 8)', () {
|
||||
test('409 VALIDATION_FAILED + message PHOTO_DUPLICATE → 归一为 PHOTO_DUPLICATE', () {
|
||||
final ex = ApiClient.mapDioException(_httpError(409, {
|
||||
'code': ErrorCodes.validationFailed,
|
||||
'message': 'PHOTO_DUPLICATE',
|
||||
'requestId': 'req-dup',
|
||||
}));
|
||||
expect(ex.code, ErrorCodes.photoDuplicate);
|
||||
expect(ex.statusCode, 409);
|
||||
expect(ex.requestId, 'req-dup', reason: '诊断字段保留');
|
||||
});
|
||||
|
||||
test('契约日后设独立 code=PHOTO_DUPLICATE → 原样透传(归一兼容演进)', () {
|
||||
final ex = ApiClient.mapDioException(_httpError(409, {
|
||||
'code': ErrorCodes.photoDuplicate,
|
||||
'message': 'duplicate photo',
|
||||
'requestId': 'r',
|
||||
}));
|
||||
expect(ex.code, ErrorCodes.photoDuplicate);
|
||||
expect(ex.statusCode, 409);
|
||||
});
|
||||
|
||||
test('非查重语义不受归一影响(同 code 不同 message / 非 409)', () {
|
||||
// 409 VALIDATION_FAILED 但 message 不是查重(如幂等参数校验)。
|
||||
expect(
|
||||
ApiClient.mapDioException(_httpError(409, {
|
||||
'code': ErrorCodes.validationFailed,
|
||||
'message': 'bytes out of range',
|
||||
'requestId': 'r',
|
||||
})).code,
|
||||
ErrorCodes.validationFailed,
|
||||
);
|
||||
// 400 VALIDATION_FAILED 是普通校验失败(非查重拒绝路径)。
|
||||
expect(
|
||||
ApiClient.mapDioException(_httpError(400, {
|
||||
'code': ErrorCodes.validationFailed,
|
||||
'message': 'PHOTO_DUPLICATE',
|
||||
'requestId': 'r',
|
||||
})).code,
|
||||
ErrorCodes.validationFailed,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('mapDioException:网络层(客户端伪码)', () {
|
||||
test('连接失败 → NETWORK_ERROR', () {
|
||||
final ex = ApiClient.mapDioException(
|
||||
|
||||
Reference in New Issue
Block a user