Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-test-k7.md
2026-08-01 12:40:53 +07:00

78 lines
9.2 KiB
Markdown

TEST-K7: IN-PROGRESS
# sub-test-k7 — Lane TEST K7 (bridge KHKK→HĐ, chuỗi SP-002 B4, S167 · 2026-08-01)
> Rào: chỉ `tests/**` · KHÔNG production code · KHÔNG commit. Ghi-TỪNG-MỤC chống-#53: xong mục nào ghi ngay mục đó; mục CHƯA CHẠY chỉ được tiêu-đề + "CHƯA ĐO".
## §0 — Tiền đề ĐO TƯƠI (không tin số đề bài)
- `git status` @spawn: `src/Backend/**` dirty 3 file + 1 file MỚI (`CreateContractFromSigningPlanFeatures.cs`) = **lane BE K7 đã LAND, uncommitted** — khớp `sub-implbe-k7.md`. `tests/**` = **0 file dirty** (baseline sạch).
- **Baseline suite ĐO TƯƠI (trước khi tôi gõ 1 chữ):** `SolutionErp.Infrastructure.Tests` = **577 total / 576 passed / 1 FAILED**. FAIL đúng test đã bàn giao:
`AuthorizePolicyRegressionTests.ContractSigningPlansController_EveryWriteEndpoint_HasAuthorizePolicy` @`:398``Expected parts[0] ... they differ: "Contracts" vs "KeHoachKyKet"`**RED tái hiện đúng nguyên nhân implbe-k7 khai**, không phải flaky.
- Đếm action controller từ ĐĨA: `grep -c "^ \[Http"` = **17** (implbe-k7 ước "16"; số đo là 17). Neo chống-vacuous `>= 15` không vỡ, giữ nguyên nghĩa.
### 🔎 Chồng-lấn TÌM TRƯỚC KHI VIẾT (bài S166) — T8 ĐÃ CÓ NGƯỜI ĐO MỘT NỬA
`Application/ContractV2PipelineTests.cs` (W6-S161) đã phủ đúng mệnh đề T8 của bảng invest (`sub-invest-fable-b2-cum3.md:82`): approver-V2-không-role-legacy thấy List/Detail + Inbox, cấp-2 KHÔNG thấy khi đang cấp-1 — `Inbox_PendingLevelApprover_SeesChoDuyetContract` · `Inbox_OtherLevel_DoesNotSeeContract` · `View_V2Approver_NotDrafter_CanSeeChoDuyetContract`.
**KHÔNG nhân bản.** Cái các test đó KHÔNG đo: chúng **seed thẳng** `Contract{Phase=ChoDuyet, ApprovalWorkflowId, stepIndex}` — tức giả định sẵn thứ mà cầu K7 phải TỰ sinh. Vế còn hở = **mối nối cầu→đường-ống**. T8 của tôi đo đúng chỗ hở đó (bridge → trình THẬT qua `ContractWorkflowService` → inbox), 0 chồng lấn.
## §1 — [BƯỚC 1 ✅ XONG] Fix bàn-giao BE: ngoại lệ `CreateContract`
File: `tests/SolutionErp.Infrastructure.Tests/Api/AuthorizePolicyRegressionTests.cs`
- Thêm **bản đồ kỳ-vọng-CHÍNH-XÁC** `KhkkCrossModuleGates = { CreateContract → "Contracts.Create" }` (KHÔNG phải danh sách "bỏ qua"): action trong bản đồ **vẫn bị đo**, chỉ đổi key kỳ vọng ⇒ gate sai thành `KeHoachKyKet.Create` **vẫn ĐỎ**. Comment nêu LUẬT (tài nguyên SINH RA quyết định khoá ghi) + trỏ gotcha **#85** + `MenuPermissionHandler.cs:40` (so khớp CHÍNH XÁC 1 key, không kế-thừa). **0 số-đếm trong comment.**
- Vế (b) tách 2 nhánh: có trong bản đồ → `authz.Policy.Should().Be("Contracts.Create")`; không → giữ nguyên luật cũ `parts[0] == MenuKeys.KeHoachKyKet` cho 16 action còn lại.
- Thêm (e) chốt-bằng-TÊN: `byName[CreateContract] == "Contracts.Create"` + `KhkkCrossModuleGates.Keys.Should().BeSubsetOf(byName.Keys)` — đổi tên/xoá action mà quên dọn bản đồ = **cấu-hình-chết im lặng** (vắng-mặt trông giống ổn) ⇒ bắt luôn.
- Neo chống-vacuous `:375` **GIỮ NGUYÊN `>= 15`** (không hạ, không đổi nghĩa; thực đo 17).
- Vế (a)/(c)/(d) không đụng: `CreateContract` là POST nên vẫn phải qua luật "verb ghi không được gate Read" + "phải Create/Update/Delete".
**ĐO:** `--filter AuthorizePolicyRegressionTests`**Passed! Failed: 0, Passed: 22** (trước: 1 FAIL). File này +0 test mới (sửa test sẵn có, +2 vế assert).
## §2 — [BƯỚC 2 ✅ XONG] File MỚI 11 test bridge — `--filter CreateContractFromSigningPlanTests` → **Passed! Failed: 0, Passed: 11**
File: `tests/SolutionErp.Infrastructure.Tests/Application/CreateContractFromSigningPlanTests.cs` (mirror khuôn bridge PE cũ `CreateContractFromEvaluationMultiWinnerTests``IdentityFixture` + deps THẬT `ContractCodeGenerator`/`ContractWorkflowService`, KHÔNG stub).
| # | Test | Đo cái gì (vế mạnh nhất) |
|---|---|---|
| T1 | `T1_MotDong_TaoDungMotHopDong_VaGhiNguocContractId` | trọn field-map + **mã HĐ so RUỘT** `{Project.Code}/HĐTP/SOL&{Supplier.Code}/01` (bằng chứng cầu ĐÃ nạp 2 bảng — vá-6) + SLA≈+7d + `Line.ContractId` + `pe.ContractId` + **2 changelog ĐÚNG 2 BẢNG**. 3 số 900/999/750 cố ý khác nhau ⇒ chỉ `Σ ApprovedAmount` đúng |
| T2 | `T2_HaiDongCungNcc_GopVaoMotHopDong_GiaTriLaTong` | gộp N (vá-5, chỉ tồn tại nhờ Mig 71 bộ-3 unique) — 1 HĐ, CẢ 2 dòng cùng `ContractId` |
| T3 | `T3_DongChuaCoGiaChot_ThiChan_KhongTaoHd_VaKhongDotMa` | message NÊU TÊN hạng mục + atomic (dòng LÀNH cũng không bị ghi) + **`ContractCodeSequences` rỗng** = rào đứng TRƯỚC codegen |
| T4 | `T4_PhieuChuaDuyetXong_ThiChan` | dòng CÓ đủ giá ⇒ chỉ đổi ĐÚNG 1 biến (phase) |
| T5 | `T5_DongDaCoHopDong_ThiChan` | double-bridge PER-DÒNG + HĐ cũ không bị ghi đè |
| T6 | `T6_HaiDongKhacNcc_ThiChan` | + **ĐỐI CHỨNG DƯƠNG cùng lượt**: tách ra bấm từng dòng thì QUA (2 HĐ) |
| T7 | `T7_QuyTrinhPinSai_Chan4Nhanh_VaQuyTrinhDungThiQua` | **ma-trận cùng-ruột đổi ĐÚNG 1 biến**: NotFound · sai-loại · [vá-3] `!IsActive` · [vá-3] `!IsUserSelectable`**4 message PHÂN BIỆT** + đối-chứng-dương quy-trình-đúng QUA |
| T8 | `T8_HdSinhTuCau_TrinhDuyet_ThiVaoDungHopThuCapDangCho` | **mối nối cầu→đường-ống** (chỗ `ContractV2PipelineTests` KHÔNG đo): trình qua máy THẬT → `levelOrder==1` (quên pin V2 = null) → Cấp-1 thấy hộp thư, Cấp-2 chưa |
| T9 | `T9_DongCuaPhieuKhac_ThiChan` | trộn dòng phiếu hàng xóm + id rác → chặn, dòng hàng xóm không bị đụng |
| T10 | `T10_BacCauLanHai_TaoHdThuHai_NhungPeGiuHdDau` | **CHIỀU của `??=` (vá-4a)**: viết `=` thay `??=` thì mọi assert khác vẫn xanh, chỉ vế này ĐỎ. + cardinality DoD vá-15 + seq mã 01→02 |
| T11 | `T11_BamHaiLanCungTapDong_ThiLanHaiBiChan_KhongTaoHdThua` | **double-click** — khoá HÀNH VI ĐANG CÓ (409 "đã có Hợp đồng", KHÔNG chế "no-op") + `LastSeq==1` (không cháy mã oan) |
**Ánh xạ bảng invest T1-T8 → 11 test:** T1→T1 · T2→T2 · T3→T3 · T4→T4 · T5→T5 · T6→T6 · T7→T7(nhánh b, +3 nhánh vá-3/NotFound) · T8→T8. **+3 test NGOÀI bảng** (T9 rào tập LineIds · T10 chiều `??=` · T11 double-click) — 2 cái sau là yêu cầu §4/§5 của đề bài.
🔴 **LỆCH SPEC ĐÃ ĐO, test theo CODE + khai trong header file:** bảng invest T1 (`:75`) ghi `WorkflowDefinitionId null`; **vá-6** (`spec-cum3:16`) ĐẢO lại "pin như 2 tiền lệ" và spec `:4` chốt "16 vá THẮNG". Mã theo vá-6 ⇒ T1 assert **pin ĐÚNG bản active của loại HĐ đó** (seed sẵn 1 bản NHIỄU loại khác ⇒ `!= null` suông sẽ không đủ).
**2 lần ĐỎ trên đường đi (đều là seed, KHÔNG phải prod):** T9 gọi `SeedAsync` 2 lần ⇒ SQLite 19 UNIQUE `ApprovalWorkflows(Code,Version)` rồi `PurchaseEvaluations.MaPhieu` → vá bằng hậu-tố ngẫu nhiên cho MỌI mã (`Projects.Code`/`Suppliers.Code`/2 bảng workflow/`MaPhieu`); T1+T10 chuyển sang so mã HĐ với `s.Project.Code`/`s.Suppliers[0].Code` đọc từ seed. **0 dòng production bị sửa.**
## §3a — [✅ XONG] Authz 2 CHIỀU — `--filter KhkkBridgeAuthzTests` → **Passed! Failed: 0, Passed: 5**
File MỚI: `tests/SolutionErp.Infrastructure.Tests/Api/KhkkBridgeAuthzTests.cs`. Chọn khuôn **RẺ NHẤT MÀ VẪN THẬT**: chạy thẳng `MenuPermissionHandler` + `UserManager`/`RoleManager` thật trên `IdentityFixture`**KHÔNG WebApplicationFactory**. Khác `AuthorizePolicyRegressionTests` (đọc ATTRIBUTE = "cửa có khoá tên gì"), file này đo **QUYẾT ĐỊNH** = "khoá đó mở cho ai".
🔴 **Điều kiện làm phép đo có răng: 2 tên policy KHÔNG hardcode** — đọc TỪ CHÍNH controller (class-level + action-level của `CreateContract`) rồi mới đem đi hỏi handler ⇒ ai sửa attribute thì test đo theo cái MỚI mà vẫn hỏi đúng câu "hai vế có phải HỘI không". Kèm vế `actionPolicy != classPolicy` ("hai vế TRÙNG NHAU thì hội-2-khoá chỉ còn 1 khoá — cửa tự nới mà nhìn vẫn như cũ").
| # | Test | Vế |
|---|---|---|
| 1 | `CauKhkkSangHd_VaiDuCaHaiVe_ThiQua` | **ĐỐI CHỨNG DƯƠNG** — thiếu nó thì 2 test 403 vẫn xanh trong thế giới "chặn tất cả" |
| 2 | `CauKhkkSangHd_VaiChiCoQuyenKeHoach_ThiBiChan` | cấp RỘNG TAY cả 4 cờ trên `KeHoachKyKet` vẫn 403 — chốt trước vế-1-PHẢI-qua để không xanh vì lý do sai |
| 3 | `CauKhkkSangHd_VaiChiCoQuyenTaoHopDong_ThiBiChan` | chiều ngược lại, cũng chốt trước vế-2-PHẢI-qua |
| 4 | `CauKhkkSangHd_Admin_KhongCanDongQuyenNao_VanQua` | khoá nhánh bypass (0 dòng `Permissions`) — chống "siết cho nhất quán" rồi khoá luôn Admin |
| 5 | `CauKhkkSangHd_UserBiVoHieuHoa_ThiChan_DuCoDuQuyen` | **cùng bộ quyền, đổi ĐÚNG 1 biến `IsActive`** ⇒ kết quả ngược (nghỉ việc mà token còn hạn) |
## §3b — Vá-7 B6 neo mới
CHƯA ĐO.
## §4 — Suite FULL
CHƯA ĐO.
END test-k7 — VERDICT=IN-PROGRESS