# test K1

## Tiến độ

### §0 Baseline ĐO TƯƠI trước khi viết (không tin số đề bài) — XONG
Lệnh: `dotnet test SolutionErp.slnx --nologo --verbosity minimal -p:BuildInParallel=false -maxcpucount:1`
```
Domain: Failed 0, Passed 45,  Total 45
Infra : Failed 1, Passed 544, Total 545
```
⇒ **baseline 590 test = 589 PASS / 1 FAIL**. Đề bài ghi "590/590" — LỆCH 1.
🔴 **FAIL KHÔNG liên quan K1:** `Forms.DocxRendererTests.RenderAsync_UnknownPlaceholder_KeepsLiteralToken_NoCrash`
→ `System.OutOfMemoryException` tại `WordprocessingDocument.AddMainDocumentPart()` (DocxRendererTests.cs:26).
Nghi flake do áp lực RAM (fan-out nhiều agent chạy song song), KHÔNG phải regression code.
Sẽ đo lại RIÊNG test đó ở §3 để phân biệt flake ⟂ vỡ thật.

### §1 Đọc nguồn — XONG
- `sub-invest-fable-b2-cum1.md:59-75` §K1.e (5 case) + §K1.f (checklist acceptance)
- Code THẬT (test theo CODE, không theo spec):
  · `Domain/Master/Catalogs/ContractCatalogEntry.cs:13-25` — 10 cột
  · `Persistence/Configurations/ContractCatalogEntryConfiguration.cs:27-29` — UNIQUE filtered `[IsDeleted] = 0` + index ApprovalGroup + query filter
  · `DbInitializer.cs:3163-3324` `SeedContractCatalogAsync(ApplicationDbContext, ILogger)` **private static** → gọi qua REFLECTION (khuôn S67)
  · `DbInitializer.cs:3293-3296` — `IgnoreQueryFilters()` khi dò existing (né #75/#76)
  · `Application/Master/Catalogs/ContractCatalogFeatures.cs` — List/Get/Create/Update/Delete + 2 validator `InclusiveBetween(1,8)`
  · `Api/Controllers/ContractCatalogController.cs:20-23` — `[Authorize]` class + `Roles="Admin"` cho 3 verb ghi (**0 policy key**)
  · `MenuKeys.cs:28-32 + :173` — const `ContractCatalog` **VÀO `All`**; `DbInitializer.cs:1776` menu row parent `Catalogs`

## PHẦN 2 (relaunch)

### §2 Tiền-đề đề bài SAI — file ĐÃ LAND trên đĩa (đo, không suy)
Đề bài relaunch nói "lượt trước chết sau khi đọc contract, **CHƯA viết test**". ĐĨA nói khác:
```
?? tests/SolutionErp.Infrastructure.Tests/Application/ContractCatalogTests.cs   (git status: untracked)
-rw-r--r-- 25005 Jul 31 14:49 ContractCatalogTests.cs      # sub-test-k1.md sửa lần cuối 14:46
```
⇒ lượt-1 VIẾT XONG file lúc 14:49 rồi chết **trước khi** cập nhật sub-md/return (đúng hình #53:
việc land, vết mất). Sub-md dừng ở "(ĐANG LÀM)" nên lead đọc thành "chưa làm".
🔴 Bài: `git status` 1 lệnh phân biệt "chưa-làm" ⟂ "làm-rồi-mất-vết" — **KHÔNG viết đè**, sẽ mất 7 test.

Khác 2 điểm so với lệnh (lượt-1 tự chốt, ghi rõ ở header file `:41-42` + `:29-39`):
- Đường dẫn `Application/` (không `Services/`) — `Services/` là chỗ code-generator + workflow-service;
  hàng xóm đúng nghĩa = `MasterCatalogFilteredUniqueTests.cs` / `*SeedTests.cs` ở `Application/`.
- **7 test** (không 6): tách thêm **T1b** `Seed_AfterSoftDeleteOfOneCode_DoesNotResurrectIt` —
  phép đo riêng cho `IgnoreQueryFilters()` @`DbInitializer.cs:3294` (né #75/#76).

### §3 Việc CÒN LẠI của lượt này = CHẠY (file chưa từng được biên dịch/chạy)
Rủi ro thật: file 25KB viết xong mà **chưa build lần nào** ⇒ có thể sai chữ ký API (skeleton nấc-2).
Đo: build + chạy filter `ContractCatalogTests` trước, rồi full-suite.

**Kết quả filter (build SẠCH ngay lần đầu — không sai chữ ký API nào):**
```
dotnet test tests/SolutionErp.Infrastructure.Tests/... --filter "FullyQualifiedName~ContractCatalogTests"
Passed!  - Failed: 0, Passed: 7, Skipped: 0, Total: 7, Duration: 8 s
```
7 test (không 6): T1 idempotent-86 · T1b không-hồi-sinh · T2 phân bố · T3 unique-filtered 2-chiều ·
T4 validator 1..8 · T5 vòng đời handler + xoá mềm · T6 menu-key/policy/grant.

### §4 Probe VI-PHÂN — chứng T1b có RĂNG (không đụng production)
File tạm `Application/_TempProbeK1.cs` (đã XOÁ) tái lập ĐÚNG biểu thức dò-tồn-tại của seeder
`DbInitializer.cs:3293-3296` nhưng **bỏ `IgnoreQueryFilters()`**, chạy trên chính DB thật sau khi
xoá mềm `A1-01`:
```
(A) CÓ IgnoreQueryFilters   → 86 mã, CÓ "A1-01"   ⇒ seeder skip ⇒ không hồi sinh
(B) BỎ IgnoreQueryFilters   → 85 mã, THIẾU "A1-01" ⇒ seeder Add lại ⇒ tổng 87 ⇒ T1b ĐỎ
Passed! - Failed: 0, Passed: 1, Total: 1
```
⇒ 2 tập KHÁC NHAU ⇒ T1b **phân biệt được 2 thế giới**, không phải assert rỗng nghĩa.
Zero-risk: không mimic production, gọi seeder THẬT, chỉ đổi 1 toán tử ở phía test.
Dọn dẹp verify 2 chiều: `ls` → No such file · `git status --short tests/` → chỉ còn đúng 1 dòng
`?? ContractCatalogTests.cs` (không sót file tạm).

### §5 FULL SUITE — đo tươi, 0 FAIL
```
dotnet test SolutionErp.slnx --nologo --verbosity minimal -p:BuildInParallel=false -maxcpucount:1
Domain: Failed 0, Passed  45, Total  45   (54 ms)
Infra : Failed 0, Passed 552, Total 552   (59 s)
```
⇒ **TỔNG 597 test / 0 FAIL** (45 + 552).

Đối chiếu với §0 baseline đo tươi đầu lượt-1:

| | Domain | Infra | Tổng | Fail |
|---|---|---|---|---|
| §0 baseline | 45 | 545 | **590** | 1 |
| §5 sau K1 | 45 | 552 | **597** | 0 |
| delta | 0 | **+7** | **+7** | −1 |

- +7 Infra khớp ĐÚNG 7 `[Fact]` của `ContractCatalogTests.cs` — không test nào bị nuốt/trùng tên.
- 🟢 **1 FAIL của §0 TỰ KHỎI**: `Forms.DocxRendererTests.RenderAsync_UnknownPlaceholder_KeepsLiteralToken_NoCrash`
  (OutOfMemoryException @`DocxRendererTests.cs:26`) nay XANH khi máy rảnh ⇒ **xác nhận FLAKE do áp lực
  RAM lúc fan-out chạy song song**, KHÔNG phải regression. Nghi ngờ §0 đóng bằng phép đo, không bằng đoán.
- ⚠️ Số đề bài ("590/590 → 596") sai cả 2 vế: baseline có 1 fail, và K1 là 7 test chứ không 6. THẬT = 597/0.

### §6 Không đụng production — không có bug cần báo
- File duy nhất thêm: `tests/SolutionErp.Infrastructure.Tests/Application/ContractCatalogTests.cs` (untracked).
- `git status --short tests/` cuối lượt: đúng 1 dòng `??` đó. `_TempProbeK1.cs` đã xoá sạch.
- KHÔNG commit (theo lệnh). KHÔNG sửa `src/Backend/**`.
- Đọc code K1 lượt này KHÔNG thấy bug. 2 điểm CỐ Ý đáng ghi (đã khoá thành invariant có răng):
  · `DbInitializer.cs:3293-3296` dùng `IgnoreQueryFilters()` — **khác** khuôn `:2970` (Projects) và
    ĐÚNG hơn khuôn đó (#75/#76); T1b khoá lại để ai "chuẩn hoá về khuôn cũ" là ĐỎ.
  · Dup-check handler (`ContractCatalogFeatures.cs:131,193`) chạy trên **active-set** ⇒ khớp index
    filtered `[IsDeleted]=0` ⇒ mã của dòng đã xoá tái dùng được (T3 khoá tầng DB, T5 khoá tầng handler).

END test-k1 — +7 test · 597 PASS / 0 FAIL
