6.3 KiB
sub-test-specialist-0 — STAGE A T0 REPRODUCE (PE budget-freeze @DaDuyet)
Role: test-specialist · Task: viết ĐÚNG 1 test T0 REPRODUCE (test-before cho bug "phiếu DaDuyet đổi số ngân sách theo bảng LIVE"). Kỳ vọng RED trên code hiện tại.
Boundary: CHỈ ghi tests/SolutionErp.Infrastructure.Tests/Services/PeBudgetFreezeTests.cs + file này. KHÔNG chạm production (src/Backend, fe-*), KHÔNG sửa test cũ.
1. Investigation (code = single source of truth)
Bug cơ chế (verified qua đọc code):
- Query
GetPurchaseEvaluationQuery(KHÔNG phảiGetPurchaseEvaluationDetailQuery— tên trong SPEC hơi lệch; query THẬT =GetPurchaseEvaluationQuery, trảPurchaseEvaluationDetailBundleDto). HandlerPurchaseEvaluationFeatures.cs:853. PurchaseEvaluationFeatures.cs:911-912—pairRecload LIVE từdb.PeWorkItemBudgets.FirstOrDefaultAsync(b => b.ProjectId == e.ProjectId && b.WorkItemId == wiKey)— KHÔNG phân biệt Phase.:964-972—PeBudgetSummaryDtobuild từpairRec(positional 17-field). Field cần assert:ProInitialAmount= arg 16 ←pairRec?.ProInitialAmount(DTO defPurchaseEvaluationDtos.cs:348)InitialAmount= arg 4 ←pairRec?.InitialAmount(CCM ban hành, DTO def:335)
- Property bundle:
PurchaseEvaluationDetailBundleDto.BudgetSummary(PurchaseEvaluationDtos.cs:260). - ⇒ mọi phiếu cùng cặp (ProjectId×WorkItemId) đọc CHUNG record live. Phiếu DaDuyet đổi số theo bản cập nhật mới nhất = BUG.
Finalize path (đưa phiếu tới DaDuyet ĐÚNG cách — KHÔNG seed thẳng):
PurchaseEvaluationWorkflowService.cs:855-873nhánh level-finalize:if (matchingLevel.AllowApproverFinalize && applyLevelFinalize)→:861 evaluation.Phase = DaDuyet. Đây là 1 trong 4 site DaDuyet (SPEC: :861/:899/:931/:1053). Hiện KHÔNG có snapshot ngân sách.- Mirror
PeApproverFinalizeTeststest #5 (AtLastSlot): workflow V2 1 Bước 1 Cấp,AllowApproverFinalize=true, approver duyệt + giá chốt (approvedPriceAmount=400tr, source="Ncc"—ValidApprovedPriceSources:954) → TỰ DaDuyet. TransitionAsyncsig:37-52(14 param,applyLevelFinalize=truedefault:51).
Commands đổi pair (SAU khi duyệt — KHÔNG có phase-guard nên chạy được mọi phase):
UpdatePeBudgetProCommand(Guid PeId, decimal? ProInitialAmount, decimal? ProAdjustmentAmount, string? ProNote)handler(IApplicationDbContext, ICurrentUser)—PeWorkItemBudgetFeatures.cs:63/80. Role-gate Admin|Procurement.UpdatePeBudgetCcmCommand(Guid PeId, decimal? InitialAmount, decimal? AdjustmentAmount, string? CcmNote)handler:134/151. Role-gate Admin|CostControl. Absolute-set (null=clear). EnsureTrackedAsync reuse pair đã seed.
Helpers reuse: IdentityFixture.CreateUserAsync(email, fullName, deptId, roles[]) · TestCurrentUser(userId, fullName?, email?, params roles) (Common) · FixedDateTime (Common/SqliteDbFixture.cs:71) · NoOpNotificationService (Services namespace, PurchaseEvaluationWorkflowServiceGuardTests.cs:188).
2. Test design (1 [Fact] T0 REPRODUCE)
- (a) seed Project + WorkItem + PeWorkItemBudget pair (ProInitial=100tr, CCM InitialAmount=200tr).
- (b) build PE ChoDuyet gắn cặp + workflow finalize; đưa tới DaDuyet QUA
svc.TransitionAsync(approver duyệt, applyLevelFinalize default true, giá chốt 400tr/"Ncc"). - (c) đổi pair LIVE: PRO 100tr→999tr (
UpdatePeBudgetProCommand), CCM 200tr→888tr (UpdatePeBudgetCcmCommand), Admin. + sanity assert record live ĐÃ đổi (chống vacuous). - (d) query
GetPurchaseEvaluationQuerybằng drafter (user thường, isDrafter=true qua authz:874/:891) → assertBudgetSummary.ProInitialAmount==100tr&&InitialAmount==200tr. - Kỳ vọng RED (code hiện tại đọc live → 999tr/888tr).
3. Kết quả chạy — ✅ RED (đúng kỳ vọng T0)
Lệnh: dotnet test SolutionErp.slnx --filter "FullyQualifiedName~PeBudgetFreeze" --nologo --verbosity minimal -p:BuildInParallel=false -maxcpucount:1
Build: succeeded (test compile sạch → mọi API call/using đúng). Kết quả: Failed! - Failed: 1, Passed: 0, Skipped: 0, Total: 1, Duration: 7 s.
Assert-failure nguyên văn (EVIDENCE):
[xUnit.net 00:00:10.89] SolutionErp.Infrastructure.Tests.Services.PeBudgetFreezeTests.DaDuyet_BudgetSummary_KeepsSnapshotValue_DoesNotFollowLivePairUpdate [FAIL]
Failed SolutionErp.Infrastructure.Tests.Services.PeBudgetFreezeTests.DaDuyet_BudgetSummary_KeepsSnapshotValue_DoesNotFollowLivePairUpdate [7 s]
Error Message:
Expected s.ProInitialAmount to be 100000000M because phiếu DaDuyet phải GIỮ ngân sách PRO tại thời điểm duyệt (100tr), KHÔNG chạy theo record live (999tr), but found 999000000.0M (difference of 899000000.0).
Stack Trace:
... at SolutionErp.Infrastructure.Tests.Services.PeBudgetFreezeTests.DaDuyet_BudgetSummary_KeepsSnapshotValue_DoesNotFollowLivePairUpdate() in ...\PeBudgetFreezeTests.cs:line 184
Failed! - Failed: 1, Passed: 0, Skipped: 0, Total: 1, Duration: 7 s - SolutionErp.Infrastructure.Tests.dll (net10.0)
Đọc kết quả:
- Fail tại
PeBudgetFreezeTests.cs:184= assert snapshot PROs.ProInitialAmount == 100tr→ found 999tr (record live). Bug tái hiện chính xác: phiếu DaDuyet chạy theo bảng live. - 2 assert TRƯỚC ĐÃ PASS (nếu fail sẽ báo dòng khác):
pe.Phase == DaDuyet(finalize path chạy đúng, KHÔNG seed thẳng) +liveRec.ProInitialAmount == 999tr/InitialAmount == 888tr(update ăn → assert (d) không vacuous). Chứng minh flow đầy đủ, CHỈ thiếu hành-vi snapshot. - Assert CCM (
InitialAmount == 200tr, dòng sau) chưa chạy tới vì FluentAssertions throw ở assert PRO đầu tiên — bình thường cho RED. Domain.Tests: "No test matches filter" = đúng (test nằm ở Infrastructure.Tests). KHÔNG phải lỗi.
Verdict: T0 RED PASS-as-designed. KHÔNG PREMISE-FAIL (không GREEN bất ngờ). KHÔNG chạm production. Sẽ GREEN sau khi implementer thêm snapshot ngân sách tại 4 site DaDuyet + display-gate đọc cột snapshot. Baseline: 509 → 510 test (test này RED tới khi fix land — test-before đúng quy trình bug-fix docs/rules.md §7).