158 lines
14 KiB
Markdown
158 lines
14 KiB
Markdown
TEST-K4A: DONE — 8/8 PASS (6 gốc + T7 finding-D + 1 gate-K4b MAJOR-A) · Infra 569→577 · Domain 45 · tổng 622 · 0 hồi quy · 0 prod-code touched
|
||
|
||
# Lane test K4a — test-after 49 row menu KHKK 8-nhóm (S166, 2026-08-01)
|
||
|
||
Agent: test-specialist. Ghi-từng-mục TRONG LÚC LÀM (luật sống-còn bài K3 engine-exit).
|
||
Mục nào chưa chạy = ghi tiêu-đề + "CHƯA ĐO", KHÔNG điền số dự-đoán.
|
||
|
||
## §0 — Trạng thái khởi động
|
||
|
||
- [x] §0.1 ĐÃ đọc khuôn. Chốt harness: seeder là `private static` → REFLECTION.
|
||
- `SeedMenuTreeAsync(ApplicationDbContext, ILogger)` — 2 tham số.
|
||
- `SeedAllRolesReviewReadPermissionsAsync(ApplicationDbContext, RoleManager<Role>, ILogger)` — 3 tham số (site-2).
|
||
- Khuôn invoke đã có sẵn ở `ContractCatalogTests.cs:459` (`InvokePrivateSeedAsync`, rm nullable) →
|
||
dùng lại HÌNH, không chế fixture mới. Fixture = `IdentityFixture` (cần RoleManager cho site-2).
|
||
- [x] §0.2 ĐÃ đọc BE đã land (git diff, chưa commit):
|
||
- `MenuKeys.cs:72-84` — `KhkkGroupNumbers=[2..8]` · `KhkkLeafActions=[WfView,List,Create,Pending,Approved,Deleted]`
|
||
· `KhkkGroupMenuKeys` = SelectMany(group → 6 leaf) · spread `.. KhkkGroupMenuKeys` vào `All:244`.
|
||
- `DbInitializer.cs:2115-2157` — vòng seed 49 row menu (parent `KeHoachKyKet`, Order nhóm = n,
|
||
leaf Order 1..6) + `:2235` labelBackfill `Khkk_G1` + `:2442-2446` site-2 `KhkkKeys().Concat(KhkkGroupMenuKeys)`.
|
||
- Đếm BY-SOURCE `All` = 113 (tự cộng tay từng dòng initializer). **Chưa phải số ĐO** — sẽ đo runtime ở T1.
|
||
- [x] §0.3 CHỒNG LẤN ĐÃ CÓ (không viết lại): `ContractCatalogTests.cs:445` T6 K1 assert
|
||
`MenuKeys.All.Except(menuKeysInDb).Should().BeEmpty()` sau `SeedMenuTreeAsync` ⇒ invariant
|
||
"49 key mới ĐỀU có menu-row" ĐÃ được test cũ phủ tự động (nó là phép thử của claim by-construction).
|
||
Lane K4a KHÔNG nhân bản; thay vào đó đo cái test cũ KHÔNG đo: hình bộ sinh · đếm 56 row họ Khkk ·
|
||
idempotent ×2 · labelBackfill · grant site-2.
|
||
- [x] §0.4 **Baseline Infra ĐO TƯƠI 2026-08-01 (trước khi thêm file): 569 PASS / 0 FAIL** (42 s).
|
||
Đo trên cây có BE K4a đã land ⇒ test T6-K1 cũ (`All.Except(menuKeysInDb)`) XANH với 49 key mới
|
||
= bằng chứng độc lập rằng vòng seed 49 row khớp đúng bộ key trong `All`.
|
||
|
||
## §1..§4 — File test đã LAND: `tests/SolutionErp.Infrastructure.Tests/Application/KhkkGroupMenuSeedTests.cs` (6 test)
|
||
|
||
Harness: reflection `private static` + `IdentityFixture` (không fixture mới). Không đụng prod code.
|
||
|
||
| # | Test | Đo cái gì | Chống vacuous bằng |
|
||
|---|---|---|---|
|
||
| T1 | `KhkkGroupMenuKeys_Is49Keys_GroupImmediatelyFollowedBySixLeaves` | 49 key · `Equal` với **mảng 49 chuỗi VIẾT TAY** (không gọi lại factory ⇒ không tautology) · liền-kề `Khkk_G{n}` ngay trước `Khkk_G{n}_WfView` · pin 6 action | kỳ vọng là literal, sai 1 ký tự là đỏ |
|
||
| T2 | `MenuKeys_All_ContainsThe49_NoDuplicate_AndKeepsGroup1Outside` | All ⊇ 49 · `OnlyHaveUniqueItems` · **`HaveCount(113)` = số ĐO runtime** · `KhkkGroupNumbers == [2..8]` · 7 key nhóm 1 KHÔNG nằm trong All | vế cuối = guard thật: thêm số 1 vào bộ sinh ⇒ `tree.Add("Khkk_G1")` lần 2 ⇒ trùng PK lúc boot |
|
||
| T3 | `SeedMenuTree_RunTwice_Has56KhkkRows_NoDuplication_AndCorrectTreeShape` | seed ×2: **56 row họ Khkk** (7 nhóm-1 + 49) · lần 2 danh sách key Y NGUYÊN · **tổng bảng MenuItems bất động** · 8 group parent=`KeHoachKyKet` Order=n · mỗi group đúng 6 con · 8 nhãn nhóm phân biệt | đếm bằng Ordinal in-memory (không phụ thuộc collation LIKE); đã kiểm KHÔNG có `HasData` cho MenuItems ⇒ 56 row đến TỪ seeder, không phải EF seed |
|
||
| T4 | `SeedMenuTree_NewGroupLeaves_MirrorGroup1_LabelIconOrder` | 42 leaf mới khớp **nhãn + icon + Order** của leaf nhóm 1 tương ứng (đo DB↔DB, không bảng hardcode) | chốt trước "6 nhãn nhóm 1 PHÂN BIỆT" — nếu chúng trùng nhau, phép so mirror đúng ở mọi thế giới |
|
||
| T5 | `SeedMenuTree_LabelBackfill_HealsGroup1Label_WithoutAddingRows_ControlKeyStaysDirty` | **MA TRẬN cùng-ruột-khác-1-biến**: bôi hỏng nhãn CẢ `Khkk_G1` (ca thật: về chuỗi skeleton S159) LẪN `Khkk_G2` trong cùng 1 lần → seed lại → G1 **tự lành**, G2 **giữ chữ hỏng**; tổng row bất động | 2 kết quả TRÁI NGƯỢC trong 1 lần chạy; khác biệt duy nhất = có/không entry `labelBackfill` ⇒ chứng đúng cơ chế backfill, không phải upsert "tiện tay" ghi Label (gotcha #11) |
|
||
| T6 | `SeedAllRolesReviewRead_GrantsReadOnNew49KhkkKeys_ReadOnly_WithPeContrast` | site-2 phát CanRead cho cả 49 key (nêu tên `Khkk_G2_List`) · C/U/D=false · 7 key nhóm 1 GIỮ quyền (`.Concat` mở rộng chứ không thay thế) | **ĐỐI CHỨNG CÙNG LƯỢT**: cùng role đó `Pe_DuyetNcc_List` có CanCreate=true ⇒ chuỗi "false" trên Khkk không phải vì seeder no-op. Chạy `SeedMenuTreeAsync` trước = smoke FK cho TOÀN grant-set |
|
||
|
||
Đường vào duy nhất của 49 key ở site-2 = `KhkkKeys().Concat(MenuKeys.KhkkGroupMenuKeys)` (K4a) —
|
||
`All.Where(StartsWith "Pe_")` không bắt `Khkk_*` ⇒ gỡ `.Concat` là T6 ĐỎ.
|
||
|
||
## §5 — Chạy filtered — ĐÃ ĐO
|
||
|
||
`dotnet test tests/SolutionErp.Infrastructure.Tests --filter "FullyQualifiedName~KhkkGroupMenuSeed" --nologo`
|
||
→ **Failed: 0, Passed: 6, Total: 6** (3 s).
|
||
Ghi chú số: `MenuKeys.All` runtime = **113** ĐÚNG như đếm by-source của lane BE (assert xanh) ⇒
|
||
`Policies` DERIVED = 113 × 4 = **452**.
|
||
|
||
## §6 — Full suite — ĐÃ ĐO
|
||
|
||
- **Infra: Failed 0, Passed 575, Total 575** (30 s) — baseline 569 + 6 = 575 ⇒ **0 hồi quy**.
|
||
- **Domain: Failed 0, Passed 45** (311 ms) — đo tươi, không lấy từ trí nhớ.
|
||
- **Tổng suite = 45 + 575 = 620.**
|
||
|
||
Caveat trung thực: giữa lượt làm có commit `7a903cf` (lane F-1, FE + `ContractSigningPlanFeatures.cs`)
|
||
land vào repo. Đã kiểm: `git diff --stat -- src/` vẫn đúng 42/79 dòng của riêng K4a như lúc mở lượt
|
||
⇒ byte nguồn K4a KHÔNG đổi trong lúc đo; file lane F-1 đã nằm sẵn trên đĩa TRƯỚC lần chạy full
|
||
(nó là ` M` từ đầu, commit chỉ chuyển trạng thái index) ⇒ con số 575 đo đúng trạng thái đĩa hiện tại.
|
||
|
||
## §7 — Rào đã giữ
|
||
|
||
- KHÔNG chạm production code: `git status` cho thấy dưới `tests/` chỉ có **1 file MỚI**
|
||
`KhkkGroupMenuSeedTests.cs`; 2 file `src/` đang ` M` là diff của lane BE K4a (có từ trước lượt này).
|
||
- KHÔNG đụng `ContractSigningPlan*` / `KhkkWorkflowPanel` / `khkk.ts`.
|
||
- KHÔNG commit, KHÔNG push.
|
||
- KHÔNG sửa `SolutionErp.slnx` (project SDK-style tự glob `.cs`; test đã build + chạy).
|
||
|
||
## §8 — Quan sát gửi lead (KHÔNG phải bug, không tự sửa)
|
||
|
||
1. **Chồng lấn có lợi:** test T6 lane K1 (`ContractCatalogTests.cs:445`) nay là phép thử tự động cho
|
||
claim "49 key ∈ All đều có menu-row" — nó XANH ở baseline 569 ⇒ claim by-construction của K4a đã
|
||
có chứng nhân độc lập, lane K4a không cần lặp lại.
|
||
2. **Nhãn G2..G8 KHÔNG tự lành** (đo ở T5, đối chứng): nếu owner đổi chữ 7 nhãn nhóm sau khi prod đã
|
||
seed thì sửa trong `khkkGroupLabels` là VÔ TÁC DỤNG — phải thêm entry `labelBackfill`. Comment
|
||
BE `:2233-2234` đã khai đúng điều này; T5 nay khoá nó thành bất-biến có răng.
|
||
3. **Số canonical cần đổi kèm K4a:** `MenuKeys.All` ĐO runtime = 113 ⇒ `docs/STATUS.md` row
|
||
`Menu keys` = 113 · `Policies` = 452. (T2 sẽ ĐỎ nếu 2 số này drift khỏi mã.)
|
||
|
||
## §D — RESUME gate K4a, finding D (MAJOR): bất-biến "revoker KHÔNG đụng `Khkk_*`"
|
||
|
||
Lệnh lead: +1 Fact `Revoke_DoesNotTouch_KhkkGroups`. Lý do: ca ĐÃ VỠ THẬT trên prod
|
||
(Run #423 — 447/494 row rơi, 11/13 role về 0; gotcha #84) mà hiện 0 test ⇒ thêm 1 chữ vào
|
||
bộ lọc revoke thì suite vẫn xanh.
|
||
|
||
- [x] §D.1 Đọc PREDICATE THẬT từ mã (`DbInitializer.cs:2554-2568`), không theo prompt:
|
||
`RoleId != adminRoleId && (MenuKey.StartsWith("Hrm") || MenuKey.StartsWith("Off")
|
||
|| MenuKey == Personal) && (bất kỳ cờ nào đang true)` → set cả 4 cờ = false.
|
||
🔴 **Bẫy vacuous-pass phát hiện khi đọc mã:** `:2546-2551` — **Admin role chưa seed ⇒ revoker
|
||
RETURN SỚM, no-op**. Test không seed Admin sẽ XANH vì revoker CHƯA TỪNG CHẠY, chứ không
|
||
phải vì `Khkk_*` được tha. Bắt buộc seed Admin + có **control dương**.
|
||
- [x] §D.2 Chọn chỗ đặt: `KhkkGroupMenuSeedTests.cs` (file của lane K4a) — đã có sẵn mảng
|
||
`Expected49` + 7 key nhóm 1 + harness seeder; đặt ở `AdminOnlyModulesRevokeTests.cs` sẽ phải
|
||
chép lại danh sách 49 key (nguồn thứ 2 = nguy cơ lệch). Vẫn MIRROR khuôn 2-vế của file đó.
|
||
- [x] §D.3 ĐÃ viết `T7 Revoke_DoesNotTouch_KhkkGroups` (cuối `KhkkGroupMenuSeedTests.cs`).
|
||
Kịch bản 1 lượt, 3 chiều phân biệt:
|
||
1. seed menu THẬT + site-2 grant THẬT cho 3 role (Admin + Drafter + DeptManager);
|
||
2. cấp tay 2 key **trong** tập ẩn (`Off_DanhBa`, `Personal`) cho cả 3 role = **CONTROL DƯƠNG**;
|
||
3. tick thêm `CanCreate` cho `Khkk_G5_Create` (mô phỏng admin bật cờ ghi trên ma trận);
|
||
4. chạy `RevokeTemporarilyHiddenModulesAsync`;
|
||
5. **Vế 1** (control dương): 2 key kia của 2 role non-Admin về **false cả 4 cờ** ⇒ chứng
|
||
revoker CÓ CHẠY (nếu đỏ = phép đo hỏng, không phải Khkk hỏng);
|
||
**Admin giữ true** ⇒ chứng vế `RoleId != adminRoleId` còn sống;
|
||
**Vế 2**: 56 key Khkk × 2 role = **112 row sống sót**, `CanRead` true, row KHÔNG bị xoá,
|
||
cờ ghi đã tick vẫn true.
|
||
Đếm bằng ĐƠN VỊ của sự cố thật (row sống sót) để thông điệp lỗi cùng ngôn ngữ Run #423.
|
||
- [x] §D.4 **CHỨNG T7 CÓ RĂNG bằng PROBE VI-PHÂN** (không được chạm prod ⇒ không fault-inject
|
||
vào `DbInitializer`): file tạm `ZzProbeTeethTemp.cs` dựng lại ĐÚNG hình câu truy vấn revoke
|
||
(`:2554-2568`) + thêm **đúng 1 vế** `|| MenuKey.StartsWith("Khkk")` rồi đo trên DB thật.
|
||
Kết quả ĐO: **trước = 112 row Khkk CanRead=true · sau bộ-lọc-bị-nới = 0** ⇒ đúng tập row mà
|
||
T7 assert sẽ rơi hết ở thế giới hỏng ⇒ T7 phân biệt được 2 thế giới, KHÔNG phải assert đúng
|
||
ở mọi thế giới. Probe chạy 1/1 PASS rồi **XOÁ**; verify 2 chiều: `git status -- tests/` chỉ
|
||
còn 1 file `??` (KhkkGroupMenuSeedTests.cs) và suite = 569+**7** (không phải +8) ⇒ 0 dư lượng.
|
||
|
||
## §9 — SỐ CHỐT (đo tươi, lần cuối, sau khi đã xoá probe)
|
||
|
||
- Filtered `~KhkkGroupMenuSeed`: **7/7 PASS**.
|
||
- **Infra: 576 PASS / 0 FAIL** (baseline 569 + 7) · **Domain: 45 PASS / 0 FAIL**.
|
||
- **Tổng suite = 621.**
|
||
|
||
## §MAJOR-A — RESUME 2: nợ gate-K4b, lọc nhóm màn "Đã xoá" (`GET /contract-signing-plans/deleted`)
|
||
|
||
- [x] **Chọn file = `ContractSigningPlanCrudTests.cs`** (KHÔNG phải file K4a của tôi): nó có sẵn
|
||
`KhkkFixture` **có `AuditingInterceptor`** — điều kiện SỐNG CÒN vì màn "Đã xoá" đọc row
|
||
`IsDeleted=1`; fixture thường xoá CỨNG ⇒ màn rỗng ⇒ test đo nhầm cơ chế (bài S155/S161-W2).
|
||
Kèm sẵn `SeedPeAsync` + `CreateAsync` + `DeleteAsync`. Dựng lại ở file K4a = nhân bản ~120 LOC.
|
||
- [x] **+1 Fact `ListDeletedContractSigningPlans_FiltersByApprovalGroup_ServerSide`** (test thứ 8
|
||
của lớp) + mở rộng helper `CreateAsync` thêm optional `approvalGroup` + helper mới
|
||
`ListDeletedAsync`. Cùng 1 phiếu PE, 2 nhóm khác nhau (1 và 3) — hợp lệ vì rào "1 PE = 1 kế
|
||
hoạch sống" khoá theo CẶP (PeId, ApprovalGroup) `:401-404`; workflow seed Code `QT-KHKK-V2`
|
||
nằm NGOÀI họ `KHKK-N{n}` nên rào (v) `:420-424` cho qua.
|
||
- [x] 5 vế đo (mỗi vế đều assert **`Total`**, không chỉ `Items` — `Total` = dấu vân tay lọc-SERVER
|
||
vì `CountAsync` chạy sau mọi `Where`; lọc client-side cho `Items` y hệt mà `Total` = cả thế giới):
|
||
(1) không-param ⇒ 2/Total 2 · (2) group=1 ⇒ 1/Total 1 đúng phiếu G1 · (3) group=3 ⇒ đúng phiếu
|
||
G3 (chứng không phải "luôn trả cái đầu") · (4) group=7 ⇒ RỖNG (lọc không khớp ≠ bỏ qua lọc) ·
|
||
(5) **nhánh ADMIN** ⇒ vẫn lọc đúng (vế lọc nằm NGOÀI khối IDOR `:884-893`; chỉ đo 1 vai thì
|
||
đẩy vế lọc vào trong `if (!admin)` vẫn xanh). +2 sanity trước phép đo chính (2 row THẬT sự
|
||
`IsDeleted` ∧ THẬT sự khác nhóm).
|
||
- [x] **CHỨNG CÓ RĂNG — RED→GREEN 2 CHIỀU, ĐO THẬT, KHÔNG CHẠM PROD:**
|
||
thế-giới-hỏng ("handler bỏ qua param") **tương đương chính xác** với `Handle(ApprovalGroup: null)`
|
||
trên MÃ THẬT ⇒ mô phỏng bằng cách tạm đổi ĐÚNG 1 chữ **trong test** (`approvalGroup: 1` → `null`).
|
||
- 🔴 RED đo được: `Failed: 1` — `Expected g1.Items to contain 1 item(s), but found 2`
|
||
(đúng chữ ký lỗi mà bản chưa vá sẽ tạo ra: 8 URL `?group=n` trả cùng tập mọi-nhóm).
|
||
- 🟢 Hoàn nguyên → `Passed: 1`. Verify không dư lượng: `grep "approvalGroup: null)"` chỉ còn
|
||
2 dòng HỢP LỆ (vế (1) không-lọc và vế (5) admin không-lọc).
|
||
Cách này chạy trên production code THẬT (mạnh hơn probe mimic của T7) và giữ trọn rào không-đụng-prod.
|
||
|
||
## §10 — SỐ CHỐT CUỐI (đo tươi sau MAJOR-A)
|
||
|
||
- Filter `~ContractSigningPlanCrudTests`: **8/8 PASS** · filter `~KhkkGroupMenuSeed`: 7/7 PASS.
|
||
- **Infra: 577 PASS / 0 FAIL** (569 baseline + 7 K4a + 1 MAJOR-A) · **Domain: 45 PASS / 0 FAIL**.
|
||
- **Tổng suite = 622.** `git status -- src/` sạch phần của tôi (0 prod-code touched).
|
||
|
||
END sub-test-k4a — VERDICT=8/8 PASS (+8 test: 7 K4a + 1 gate-K4b MAJOR-A, suite 622 = 45 Domain + 577 Infra, 0 hồi quy, 0 prod-code touched)
|