13 KiB
TEST-K7: DONE — suite 639/0 (45D+594I, Δ+17) · 0-FLAG-production · 2-khai (dòng-1 lead ghi-đè on-behalf — vai chết đúng lúc closing, END-line + §0-§5 vai tự ghi trọn)
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ớpsub-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>= 15khô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ànhKeHoachKyKet.Createvẫ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.KeHoachKyKetcho 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
:375GIỮ NGUYÊN>= 15(không hạ, không đổi nghĩa; thực đo 17). - Vế (a)/(c)/(d) không đụng:
CreateContractlà 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 — [✅ XONG] Vá-7 B6 neo mới = T12 end-to-end — --filter CreateContractFromSigningPlanTests → 12/12 (11 + T12)
T12_DiTronDuongFinalizeRoiBacCau_GiaTriLaSoDaCHOT_VaKhongDoiVeSau (cùng file §2).
KHÔNG chỉ "thêm 1 assert vào test K3" — lý do khai rõ: test K3 dừng ở "finalize GHI ApprovedAmount"; cầu K7 thì ĐỌC cột đó; mối nối giữa 2 vế chưa ai đo, mà chính nó là điều kiện sống của happy-path (invest :154: "không có K3 thì ApprovedAmount NULL 100%" ⇒ cầu chặn mọi phiếu). T12 cho phiếu đi TRỌN đường qua ContractSigningPlanWorkflowService THẬT (trình → duyệt → terminal chốt giá) rồi mới bắc cầu — KHÔNG seed thẳng Phase/giá (seed thẳng = tự đưa đáp án rồi chấm).
- neo (a)
Phase == DaDuyetdo MÁY tự đi tới. - neo (b) tương phản chống-rỗng: dòng A
Approved=111chốt sẵn (Proposed=999) · dòng BApproved=null(Proposed=250) ⇒ sau finalize phải là {111, 250} — chứng??=GIỮ số người có thẩm quyền chốt tay, không bị 999 đè. - neo (c)
GiaTri == 361= 111+250. ΣProposed= 1249; lấy nhầm nguồn ra 1249/1109/500 — mọi cách sai đều KHÁC 361. - vế cuối theo đúng chữ vá-7: sau bắc cầu, bấm
approvelần nữa →ConflictException(phiếu terminal) và đọc lạiLine.ContractIdKHÔNG đổi +ApprovedAmountKHÔNG đổi.
§4 — [✅ XONG] Suite FULL dotnet test SolutionErp.slnx --nologo
Passed! - Failed: 0, Passed: 45, Total: 45 — SolutionErp.Domain.Tests
Passed! - Failed: 0, Passed: 594, Total: 594 — SolutionErp.Infrastructure.Tests
| Domain | Infra | Tổng | FAIL | |
|---|---|---|---|---|
| Baseline (đo tươi @§0) | 45 | 577 | 622 | 1 (bàn giao BE) |
| Sau K7-test | 45 | 594 | 639 | 0 |
| Δ | 0 | +17 | +17 | −1 |
+17 = 12 (CreateContractFromSigningPlanTests, T1-T12) + 5 (KhkkBridgeAuthzTests). File AuthorizePolicyRegressionTests +0 test (sửa test sẵn có: 1 FAIL → PASS, +2 vế assert).
- Flaky đã khai
DeleteContractSigningPlan_SoftDeletes: KHÔNG nổ lượt này (0 Failed, chạy 1 lần duy nhất — không phải chạy lại). - Dấu chân trên đĩa (
git status): đúng 3 filetests/**(1 M + 2 ??).src/Backend/**+fe-*/**dirty là của lane BE/FE, tôi không chạm 1 dòng nào. KHÔNG commit.
§5 — FLAG cho lead
0 FLAG bug production. 12 test bridge + 5 authz đều xanh trên mã BE hiện tại; không có test nào phải giữ ĐỎ.
2 việc KHAI để lead phân xử (không phải bug, là chỗ lệch giấy tờ / vùng chưa phủ):
- 🟡 Lệch spec đã đo (giấy tờ, không phải mã): bảng invest T1
:75ghiWorkflowDefinitionId null— vá-6 thắng và mã đang pin V1. Tôi test theo CODE + vá-6, ghi mismatch trong header file test. Nếu lead muốn giữ chữ của bảng invest thì phải sửa MÃ (không phải sửa test). - 🟡 Vùng KHÔNG phủ được bằng unit test, thuộc acceptance sau restart:
SeedContractCreateAccessAsync(DbInitializer.cs:2757-2822) — grantContracts.Createchỉ ăn @restart API prod.KhkkBridgeAuthzTestschứng "vai có 2 vế thì qua / thiếu 1 vế thì 403" nhưng KHÔNG chứng seeder THẬT cấp đúng vai (Drafter/Procurement/Admin) và cấp SAU revoker (#84). Nghiệm thu vẫn phải là đếm rowPermissions WHERE MenuKey='Contracts' AND CanCreate=1sau restart thật — đúng như implbe-k7 §1 đã khai.
END test-k7 — VERDICT=DONE (suite FULL 639/0 = 45 Domain + 594 Infra · Δ+17 · 1 FAIL bàn-giao đã đóng · 0 file production chạm · chưa commit)