diff --git a/.claude/agent-memory/test-specialist/MEMORY.md b/.claude/agent-memory/test-specialist/MEMORY.md index cace3ae..2b9deea 100644 --- a/.claude/agent-memory/test-specialist/MEMORY.md +++ b/.claude/agent-memory/test-specialist/MEMORY.md @@ -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: 501 tests = 501 PASS (45 Domain + 456 Infra) as of S117 (was 495@S116; +5 PE ProMinMax [own] +1 endsBeforeCeo-pointer-aware [em-main-solo, PeFinalizeProjectionTests], filtered-run 15/15) +## 📊 Baseline: 505 tests = 505 PASS (45 Domain + 460 Infra) as of S118 (was 501@S117; +4 Infra Procurement master-access seeder isolation regression, filtered-run 4/4) - Count history + verbose per-session test-design notes (S54→D2): [archive/baseline-history.md](archive/baseline-history.md) (L2, on-demand). - Latest (D2): +6 PE Mig 58 multi-winner — `CreateContractFromEvaluationMultiWinnerTests.cs` (5, 1→N HĐ per IsWinner, GiaTri per-winner=quote-total / single=legacy-budget-sum) + `PeListWinnerNamesProjectionTests.cs` (1, EF collection-projection translation-smoke). No prod bug. > Pattern S67: private-static seed/init → invoke qua REFLECTION (`GetMethod(name, NonPublic|Static)` + `Invoke(null, [db, roleManager, NullLogger.Instance])`); seed MenuItem rows TRƯỚC Permission (FK MenuKey→MenuItem.Key Cascade, SQLite Error 19 nếu thiếu). Cycle-guard test: SqliteDbFixture đủ (no User); rollup-count test cần IdentityFixture. @@ -56,6 +56,7 @@ Test theo CODE (single source truth), document mismatch header comment + report. ## 📅 Recent activity (one-line FIFO — verbose in [archive/baseline-history.md](archive/baseline-history.md)) +- **S118 (2026-07-14) Procurement master-access seeder SCOPE-ISOLATION regression (test-after SECURITY — anh Kiệt FDC) — 501→505 PASS (+4 Infra), no-red, NO prod bug:** new `ProcurementMasterAccessSeedTests.cs` (4 [Fact]) guard seeder MỚI `SeedProcurementMasterAccessAsync` (private static, DbInitializer) — NGOẠI-LỆ role Procurement mở lại Danh mục SAU S92 revoke ("chỉ Admin thấy"). Chain thật: Revoke→SeedProcurement. **T1 upgrade-path** Procurement sau revoke→seed đúng ma trận (Suppliers R+C+U D=false + 8 read-only chỉ R). **T2 ⭐SCOPE-ISOLATION (core)** DeptManager đứng RIÊNG (KHÔNG kèm Procurement): pre-grant→revoke→seed → 9 key VẪN all-false (row tồn tại giữ vết) = grant KHÔNG rò role khác; +CONTRAST assert Procurement THẬT được nâng (chống vacuous-pass). **T3 insert-path** DB mới no-revoke → INSERT 9 row, Delete=false + count==9. **T4 idempotent** seed ×2 → bất biến + count==9 no-dup + role khác vẫn ẩn. Reflection harness MIRROR `AdminOnlyModulesRevokeTests` (NonPublic|Static, sig (ApplicationDbContext,RoleManager,ILogger), FK seed MenuItem→Role→Permission). No user cần (perm role-level). **Reflection KHÔNG vướng — KHÔNG cần đổi visibility.** KHÔNG touch prod. - **S117 (2026-07-14) PE "duyệt theo KHOẢNG" ProMinMax range-price TEST-BEFORE-MERGE (financial-critical) — 495→500 PASS (+5 Infra, file 10→15), no-red, NO prod bug:** feature anh Kiệt: PRO có cả Min&Max → người duyệt cấp cuối chọn CẢ HAI (source="ProMinMax") → `ApprovedPriceAmount=Min`, `ApprovedPriceMaxAmount=Max`. Bất-biến `Max!=null ⟺ Source=="ProMinMax"`. Prod đã land: `ApplyApprovedPriceOnFinalize(...,decimal? maxAmount=null)` +`ValidApprovedPriceSources`+"ProMinMax"; `TransitionAsync`/`ApproveV2Async` +`approvedPriceMaxAmount` (SAU source). 5 new [Fact] qua reachable terminal path (mirror case-1 SeedSingleApproverWorkflow, KHÔNG gọi private): (a) happy Min100/Max130 set cả 2 · (a-boundary) **min==max OK** lock guard `>` vs `>=` (revert→RED) · (b) thiếu Max→Conflict "*phải có cả giá Min và giá Max*" not-finalized · (c) min>max→Conflict "*không được lớn hơn giá Max*" · (d) nguồn ĐƠN+Max→Conflict "*kèm giá Max*" wire-bug-guard. +regression assert `Max==null` vào Theory 3b (4 nguồn đơn). **⚠️ Gotcha (self-inflicted-avoided):** prod method 5-param nay → `InvokeApply` reflection helper phải pass 5 args (`{...,maxAmount}`); optional param KHÔNG auto-bind qua `MethodInfo.Invoke` → nếu giữ 4 args = `TargetParameterCountException` phá unit-test 4/4b/4c. Đã fix helper. KHÔNG touch prod. - **S116 (2026-07-13) PE báo giá SỐ ÂM regression-lock (test-before cho guard R2-revised — critical-algo/financial) — 486→495 PASS (+9 Infra), no-red, NO prod bug:** feature anh Kiệt cho ThanhTien ÂM (phát-sinh-giảm/hoàn tiền NCC = giá THẬT). BE ĐÃ đổi submit-guard `:202-208` `Sum()<=0`→`Count==0`(chưa chọn)/`else All(x==0)`(chưa có giá). 4 nhóm test khớp hành-vi MỚI: **T1** `PeWorkItemBudgetTests` (+1) quote ÂM −20tr KHÔNG chạm PeWorkItemBudget record (FullAmount ngân sách bất biến) NHƯNG CHẢY vào CurrentProposalTotal=−20tr (chứng tách-biệt cột đề-xuất vs ngân-sách, "0 code" kiến-trúc). **T2** `PeMultiWinnerTests` (+2) winnerQuoteTotal=SUM CÓ DẤU khác-winner(+50,−20=30) + cùng-winner-2-dòng(80,−30=50). **T3** `PeSubmitGuardAndBypassTests` (+4, +2 helper `SeedWinnerWithQuotesAsync` params + `BuildV2PeReadyToSubmitWithQuotesAsync`): all-zero[0,0]→BLOCKED "chưa có giá"; pure-neg[−500k]→ChoDuyet; mixed net>0→ChoDuyet; 🔒 **mixed net==0 từng-dòng≠0 [+500k,−500k]→ChoDuyet** = ranh giới phân-biệt All(==0) vs Sum()<=0 (revert→RED). **T4** `PeCcmThresholdFinalizeTests` (+2, +helper 2-signed-quote): NET threshold — [+1.2tỷ,−0.3tỷ]=0.9tỷ