[CLAUDE] PurchaseEvaluation: rang buoc du 4 thong tin muc 3 moi gui duyet + bypass nguoi soan trong chuoi duyet (UAT anh Kiet S60)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m38s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m38s
- Rename muc 3: "Chon NCC / TP thang thau" -> "Don vi NCC/TP duoc chon" (anh Kiet chot chu) x2 app + wording phu nhat quan - Guard gui duyet du CA 4 (anh chot): don vi duoc chon + gia chao thau >0 + ngan sach (Budget link HOAC nhap tay) + bang so sanh dinh kem + BE ConflictException gop moi muc thieu 1 lan, ap ca Admin (TransitionAsync submit branch) + FE pre-check missingForApproval cung predicate -> disable nut + tooltip liet ke du (computeGiaChaoThau extract single-source) - Bypass drafter-in-chain (luat GENERIC theo cap, anh chot): V2-only, BUOC DAU only - nguoi soan la approver cap k -> auto qua Cap 1..k khi gui + Audit 3 tang: Approval row AutoApprove per cap + LevelOpinion CHI slot chinh chu (khong gan chu ky NV bi skip) + Changelog + Pointer: k<max -> Cap k+1; het buoc -> Buoc 2 Cap 1; workflow 1 buoc -> terminal DaDuyet + TraLai resubmit ap lai idempotent (opinion UPSERT) - Tests: +14 PeSubmitGuardAndBypassTests (240 -> 254 PASS) - Reviewer die mid-run (gotcha #53 class) -> em main self-gate evidence-checklist PASS 0 blocker Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@ -15,7 +15,7 @@ WRITE specialist độc quyền `tests/**`. xUnit + FluentAssertions 7.2 + EF SQ
|
||||
- ❌ NOT: production code `src/Backend/**` + `fe-*/**` → test reveal bug → REPORT em main, KHÔNG fix
|
||||
- ❌ NOT: decide WHAT to test (test plan) → em main + reviewer chốt priority
|
||||
|
||||
## 📊 Baseline 240 tests = 240 PASS (58 Domain + 182 Infra) ← S57bis +12 (PeWorkItemGuardTests: 3 validator + 4 create-FK-guard + 5 update-null-safe). Pre = 228 (S56).
|
||||
## 📊 Baseline 254 tests = 254 PASS (58 Domain + 196 Infra) ← S60 +14 (PeSubmitGuardAndBypassTests: 8 Section-3 guard + 6 drafter-bypass). Pre = 240 (S57bis).
|
||||
Run: `dotnet test SolutionErp.slnx --nologo --verbosity minimal -p:BuildInParallel=false -maxcpucount:1` (MSBuild OOM → serialize build)
|
||||
|
||||
### ⚠️ Pattern: deduction hook FK → seed LeaveType cho terminal test (S43)
|
||||
@ -53,6 +53,7 @@ Test theo CODE (single source truth), document mismatch header comment + report.
|
||||
|
||||
## 📅 Recent activity (last 10 FIFO)
|
||||
|
||||
- **2026-06-12 (S60 UAT anh Kiệt — 2 feature PE submit branch, test-after build PASS):** +14 test `tests/.../Services/PeSubmitGuardAndBypassTests.cs` → **240→254 PASS** (58 Domain + Infra 182→196, 0 fail). Mirror `PurchaseEvaluationWorkflowServiceGuardTests` (IdentityFixture+SQLite, reuse `NoOpNotificationService` internal). **F1 Section 3 guard (8):** submit branch (DangSoanThao/TraLai→ChoDuyet) build `missing` list 4 mục → ConflictException msg gộp prefix `'Chưa đủ thông tin mục 3 "Đơn vị NCC/TP được chọn"...'` + join `' · '`. Cover: thiếu cả 4 / winner-only / winner+quote=0 / budget (cả null+manual=0) / comparison / **attachment gắn NCC (PES_Id!=null) KHÔNG đếm bảng so sánh = vẫn Conflict** (predicate PES_Id==null) / đủ-4-manual-budget→ChoDuyet / đủ-4-BudgetId→ChoDuyet. **F2 drafter-bypass (6, V2-only `ApplyDrafterBypassOnSubmitAsync`):** k=drafterSlots.Max(Order) bước đầu → auto Cấp 1..k. Cover: drafter=TP(2/2)+2bước→StepIdx=1/Lvl=1+opinion 1 row slot TP+2 AutoApprove / drafter=NV(1/2)→Lvl=2 cùng bước+opinion slot NV / drafter ngoài bước đầu→KHÔNG bypass StepIdx=0 Lvl=1 0-auto / 1-bước+drafter cấp cuối→DaDuyet pointers null SLA null / V1(awId null)→submit OK no-bypass no-crash / TraLai-resubmit→bypass áp lại opinion UPSERT 1 row + approval cộng dồn 2 vết. **⚠️ GUARD-FIRST:** mọi bypass-test PHẢI dựng PE đủ 4 ĐK Section 3 (winner+quote>0+manual-budget+comparison-attach) qua guard. **Seed pattern S60:** `SeedWinnerWithQuoteAsync`(PES+Detail+Quote ThanhTien) map winner→quote sum · `SeedComparisonAttachment`(PES_Id=null) · `SeedWorkflowAsync(Guid[][] stepApprovers)` build multi-step V2 1-NV/cấp. **Opinion-only-ownSlot invariant:** bypass cấp NV skip KHÔNG ghi opinion (chỉ Approval AutoApprove + Changelog vết); assert `opinions.HaveCount(1)` + `ApprovalWorkflowLevelId==drafterSlot.Id`. **No prod bug** — code đúng spec, test theo CODE (S34 rule). Tag [s60, pe-submit-guard, section3-completeness, drafter-bypass, v2-only, guard-first, opinion-ownslot-only, test-after].
|
||||
- **2026-06-11 (S57bis P2 PE WorkItemId guard Mig 49 — test-after, code đã đúng sẵn):** +12 test `tests/.../Application/PeWorkItemGuardTests.cs` → **228→240 PASS** (58 Domain + Infra 170→182, 0 fail). PE `Guid? WorkItemId` loose-Guid (KHÔNG FK vật lý, convention giống ProjectId). **Cover-map 3 trục:** (1) **Validator ×3** — `CreatePurchaseEvaluationCommandValidator.Validate(cmd)` plain API (KHÔNG có FluentValidation.TestHelper package): null→invalid+error trên WorkItemId / present→valid. (2) **Create-FK-guard ×4** — handler 4-dep instantiate THẬT trên SQLite (`new PurchaseEvaluationWorkflowService(db,dt,notify,um)` + `new PurchaseEvaluationCodeGenerator(db,dt)` — Serializable-tx non-issue SQLite proven S52; reuse `NoOpNotificationService` internal từ ...Services ns + IdentityFixture): bogus-Guid→Conflict / inactive→Conflict / active→OK+`saved.WorkItemId==active.Id`. (3) **Update-null-safe ×5 (bug-class S42 picker)** — `UpdatePurchaseEvaluationDraftCommandHandler(db,cu)` 2-dep nhẹ: request.WorkItemId=null→GIỮ w1 KHÔNG null-hoá (⭐ core) / W2-active→đổi / bogus→Conflict+giữ w1 (AsNoTracking re-read DB-truth) / inactive→Conflict / same-as-existing→skip-lookup-success. **⚠️ SPEC-DRIFT FOUND (test theo CODE, S34 rule):** `NotEmpty()` trên `Guid?` (nullable) chỉ bắt `null`, KHÔNG bắt `Guid.Empty` (FV 7.2 so default(Guid?)==null) → Guid.Empty PASS validator. KHÔNG phải lỗ hổng — create handler FK-guard (`is Guid wiId` true cho Empty + AnyAsync false) chặn → Conflict. Test LOCK behavior (1 validator-test assert Empty pass + 1 handler-test chứng minh defense-in-depth catch). REPORT em main: validator một mình không reject Guid.Empty, dựa handler. No prod bug — code đúng spec, defense-in-depth layered. Tag [s57bis, p2, pe-workitemid, mig49, validator-plain-api, null-safe-partial-update, guid-empty-nullable-notempty-drift, defense-in-depth].
|
||||
- **2026-06-09 (S56 GOLIVE-HARDEN TEST stage — 4 pre-golive fixes, test-after build):** +12 test → **216→228 PASS** (58 Domain + Infra 158→170, 0 fail). Build stage đã land prod fixes (CONTRACT từ build, signatures UNCHANGED). **#3 LeaveBalance lost-update fix:** handler terminal nay increment `db.LeaveBalances.Where(...).ExecuteUpdateAsync(s=>s.SetProperty(b=>b.UsedDays, b=>b.UsedDays+p.NumDays))` server-side + 1 explicit tx (READ COMMITTED, NO IsolationLevel). **⭐ GOTCHA: ExecuteUpdateAsync BYPASS change tracker** → instance bal tracked (Add STEP1 hoặc pre-seed cùng context) GIỮ UsedDays PRE-increment. **4 test cũ LeaveBalanceTests (case 1/2/3/4 line 163/201/240/269) FAIL ở baseline = stale-tracked-read, KHÔNG regression** (spec TEST GUIDANCE đã tiên đoán). Fix = `.AsNoTracking()` re-read (hoặc `ChangeTracker.Clear()`). +2 new: `TwoSeparateRequests_BothTerminal_UsedDaysAccumulates_NotOverwrites` (3+5=8 chứng minh increment accumulate KHÔNG overwrite = race-free invariant) + `Approve_AlreadyDaDuyet_ReApprove_ThrowsConflict_NoDoubleDeduct` (early guard Status!=DaGuiDuyet:296 → exactly-once, balance vẫn 3 not 6). **#4 Travel/Vehicle ApproveV2 smoke (WorkflowAppApproveV2Tests.cs +4):** mỗi module Submit→Approve→DaDuyet happy + outsider→Forbidden. ApplicableType Travel=9 prefix `DT/CT`, Vehicle=7 prefix `DX/XE`. Travel/Vehicle KHÔNG trừ balance → không seed LeaveType. Helper mới `SeedWorkflowForTypeAsync(type,code,...approverIds)`. **#5 ItTicket existence-oracle (ItTicketReassignAuthzTests.cs +2):** authz reorder (Forbidden TRƯỚC NotFound) — non-IT non-admin nhận Forbidden cho ticketId tồn tại VÀ không tồn tại (cặp 5b/5c phản hồi giống nhau = no oracle leak). Reorder KHÔNG vỡ test cũ (Case5 đã expect Forbidden; TicketNotFound dùng Admin caller pass authz hợp lệ). **#6 DocxRenderer (Forms/DocxRendererTests.cs NEW +4):** 0 test trước đó. MainDocumentPart null→`InvalidOperationException("*MainDocumentPart*")` (OpenXml 3.5.1 `WordprocessingDocument.Create(path,type)` tạo package RỖNG no main part) + placeholder replace happy + unknown-key giữ literal + null-value→empty. **⚠️ test helper ExtractBodyText: tránh `MainPart!.Document.Body!` (CS8602 warning) → dùng `?.Document?.Body` + `.Should().NotBeNull()`.** No prod bug found — tất cả fixes là build-stage, tôi WRITE test theo CONTRACT. Tag [s56, golive-harden, executeupdate-tracker-bypass, asnotracking-reread, travel-vehicle-smoke, existence-oracle, docxrenderer].
|
||||
↳ **[em main post-review S56]** Shipped tx = `IsolationLevel.Serializable` (code `LeaveOtApprovalFeatures.cs:369`), KHÔNG READ COMMITTED — entry's '(READ COMMITTED, NO IsolationLevel)' = build-stage snapshot, **superseded** post-review (SQLite test path unaffected — codegen Serializable already green).
|
||||
|
||||
Reference in New Issue
Block a user