4.2 KiB
4.2 KiB
sub-d2-test-4 — ĐỢT 2: test đường xóa-khi-đang-duyệt + màn "Đã xóa"
Diary GHI TRONG LÚC LÀM (run này đã mất 1 diary 0-byte + 9 return). Flush từng phần, tự
wc -csau mỗi lần ghi.
0. Trạng thái nhận việc
- Baseline lead đo: 551 PASS / 0 FAIL (Domain 45 + Infra 506). Code đợt 2 đã land, 0 test.
- Mục tiêu: 10 test (T21, T22, T22b, T22c, T23, T24, T24b, T25, T25b, T26) ⇒ đích > 551.
1. Đọc vật cần test — ĐÃ XONG
PeSoftDeleteFeatures.cs (file mới, 249 dòng) — 2 vật
A. DeletePurchaseEvaluationByApproverCommandHandler (:50-145) — thứ tự rào:
!IsAuthenticated || UserId is null→UnauthorizedException- PE không tồn tại →
NotFoundException Phase != ChoDuyet→ConflictExceptionApprovalWorkflowId is not Guid→ConflictException- steps rỗng /
CurrentWorkflowStepIndexngoài biên →ConflictException pendingLevelGroup.Count == 0(Bước không có Cấp đang chờ) →ConflictException- KHÔNG match
ApproverUserId == actorId→ForbiddenException("chưa tới lượt") ← T24 !matchingLevel.AllowApproverDelete→ForbiddenException("chưa được bật quyền") ← T23, T24b- Ghi
PurchaseEvaluationChangelog(Action=ChangelogAction.Delete, EntityType=Workflow, EntityId=matchingLevel.Id, PhaseAtChange=ChoDuyet, ContextNote=Reason.Trim() hoặc null) ← T26 db.PurchaseEvaluations.Remove(pe)= XÓA MỀM quaAuditingInterceptor(Deleted→Modified + IsDeleted=true)
🔴 Điểm sống-còn cho test: rào (7) đọc PER-ROW pendingLevelGroup.FirstOrDefault(l => l.ApproverUserId == actorId), rào (8) đọc matchingLevel.AllowApproverDelete — không g.Any(...). Đây đúng là thứ T24b phải ghim.
B. ListDeletedPurchaseEvaluationsQueryHandler (:153-248):
IgnoreQueryFilters().Where(x => x.IsDeleted)← T22b ghim vế.Where(gỡ nó ⇒ list toàn phiếu sống)- INNER join
db.Projects(❗ phiếu phải có Project THẬT, không Guid ngẫu nhiên — nếu không row rơi khỏi kết quả và test xanh giả) - IDOR-scope khi
!Roles.Contains(Admin):DrafterUserId == userId∥eligiblePhases.Contains(Phase)∥ (Phase != DangSoanThao ∧ pinned wf ∈ workflow mà user là approver) ← T22c
PeBudgetAccumulator.cs — số học cho T21 (🔴 KHÔNG phải "cả 4 cùng giảm")
| Đại lượng | Hàm | Điều kiện phase | Phiếu ChoDuyet có winner đang xét |
|---|---|---|---|
PrevSubmitted* |
ComputeAsync :53-59 |
ChoDuyet ∥ DaDuyet |
✅ CÓ vào ⇒ xóa thì ↓ |
PrevSelected* |
ComputeAsync :61-62 |
DaDuyet ∧ có winner |
❌ không (đòi DaDuyet) ⇒ KHÔNG đổi |
PendingSubmitted* |
ComputePendingAsync :104-108 |
loại tường minh ChoDuyet |
❌ không ⇒ KHÔNG đổi |
PendingSelected* |
ComputePendingAsync :114-117 |
∉{DangSoanThao,DaDuyet,TuChoi} ∧ winner | ✅ CÓ vào ⇒ xóa thì ↓ |
PriorPes |
:128-133 |
∉{DaDuyet,TuChoi} | ✅ CÓ ⇒ mất đúng 1 phần tử |
Cơ chế triệt tiêu: Remove ⇒ IsDeleted=true ⇒ global filter PurchaseEvaluationConfiguration.cs:84 loại khỏi db.PurchaseEvaluations ⇒ CẢ 4 phép cộng đọc hụt. Accumulator KHÔNG sửa.
Controller (PurchaseEvaluationsController.cs)
:163-169DELETE {id}/by-approver+[Authorize(Policy = "PurchaseEvaluations.Delete")]← T25:174-182GET deleted+[Authorize(Policy = "PurchaseEvaluations.Read")]:151-156DELETE {id}(nháp) — KHÔNG policy, đừng nhầm ⇒ T25 phải assert cả vế này (nếu ai đó "sửa" bằng cách gắn policy lên method nháp thì T25 vẫn phải phân biệt được 2 method).
2. Kế hoạch file test
- File MỚI
tests/SolutionErp.Infrastructure.Tests/Application/PeDeleteByApproverTests.cs— T21..T24b, T25b, T26 (handler-level, SQLite thật). - T25 (403 policy) = Pattern 10 reflection, thêm vào file mới cùng chỗ (đọc attribute trên method, không phải class — 2 endpoint này gắn method-level).