# Session 45 — HRM test-gap stabilization + Holiday drift fix (Mig 43)

**Date:** 2026-06-01
**Scope:** Tests + Infra (schema)
**Commits:** `051b62b` (Tests) → `0c5a014` (Mig 43) → docs (this session-end)
**CI:** Gitea Run #368 (run_number 254) PASS ~4m20s — verified prod
**Test:** 154 → **181 PASS** (58 Domain + 123 Infra, 0 fail / 0 skip)

---

## Bối cảnh + quyết định

Anh giao quyền chọn thứ tự việc. Em main chọn **"stabilize before extend"**: đóng 3 test-gap deferred từ S35-S38 TRƯỚC khi chồng schema Phase 11 mới (P11-C). Lý do: 1 gap CRITICAL-flagged, nhỏ + low-risk, deferred nhiều session; làm xong baseline vững rồi mới thêm Mig.

## Việc done

### 1. Đóng 3 test-gap (commit `051b62b`, +27 test)
🟪 **test-specialist** viết (em main chốt test plan sau recon 8 file):

- **Gap1 (CRITICAL) — `HrmConfigHolidayTests.cs` (7 test):** Holiday composite UNIQUE (Year, Date). Create duplicate → Conflict; same-date-diff-year → OK; Update→occupied-slot → Conflict; ⭐ **self-update giữ (Year,Date) đổi Name → KHÔNG false-positive** (guard short-circuit `entity.Year!=req.Year || entity.Date!=req.Date`); Update→empty-slot OK; Update non-existent → NotFound; soft-delete exclusion.
- **Gap2 (MAJOR) — `EmployeeSatelliteTests.cs` (10 test):** 5× FK-invariant (mọi Create satellite guard parent `AnyAsync(Id && !IsDeleted)` → NotFound); soft-deleted parent → NotFound; happy path FK đúng; Delete soft + DeletedBy + re-operate NotFound; **DeleteEmployeeProfile soft KHÔNG cascade satellite** (behavior hiện tại — handler chỉ soft-delete parent); EF model `DeleteBehavior.Cascade` config assertion (lock schema intent).
- **Gap3 (MAJOR) — extend `AuthorizePolicyRegressionTests.cs` (10 test):** 2 controller shape KHÁC nhau lock đúng intent — `HrmConfigsController` (class `[Authorize]` trần Policy/Roles null + writes `Roles="Admin"`) vs `EmployeesController` (class `Policy="Hrm_HoSo.Read"` + per-action Create/Update/Delete + 1 satellite representative).

⚠️ **gotcha #53 recurrence:** test-specialist return truncated mid-MEMORY-update ("let me read it first"). → em main verify-on-disk (glob + git status + đọc 3 file line-by-line + dotnet test) thay vì tin output cụt. MEMORY proxy-updated bởi em main.

### 2. Holiday drift fix (commit `0c5a014`, Mig 43)
👤 **em main solo** (bug-fix reasoning chain + schema decision tightly coupled → tránh spawn-truncation cho change nhỏ).

- **Bug Gap1 lòi ra:** `HolidayConfiguration` DB UNIQUE `(Year,Date)` plain `.IsUnique()` KHÔNG filter, trong khi handler check `!IsDeleted` → admin xoá (soft) 1 ngày lễ rồi tạo lại cùng (Year,Date) → app-check PASS nhưng DB UNIQUE reject → `DbUpdateException` **500 reachable**.
- **Fix:** `.HasFilter("[IsDeleted] = 0")` — khớp pattern **13× sẵn có** (Catalogs ×4, Contract/PE/Proposal/Budget/WorkflowApps code-unique). **Mig 43 `FilterHolidayUniqueIndexByIsDeleted`** (DropIndex + CreateIndex filter; Down reverse sạch). Applied Dev + Design LocalDB.
- **Test flip:** Case 7 từ assert `DbUpdateException` → assert SUCCESS (reuse slot, 1 active + 1 soft-deleted). Spec-change → update test cùng commit.
- Table count vẫn **91** (index-only mig, no CREATE TABLE).

### 3. cicd verify (🟩 Run #368 PASS)
test gate 181 · Mig 43 applied prod (`__EFMigrationsHistory` top) · `IX_Holidays_Year_Date` `filter_definition = ([IsDeleted]=(0))` live (was NULL) · FE bundle UNCHANGED `Krjvg_3j`/`6sNStgxa` (đúng — BE-only push, KHÔNG flag ship-fail) · health 200 · 0 regression.

### 4. P11-C pre-flight (🟦 investigator-codebase)
Vehicle+Driver catalog: chưa có master entity (chỉ VehicleBooking free-text Mig 39). **Recommend:** extend HrmConfigs +2 kind (`vehicles`+`drivers`) declarative KIND_CONFIG, Mig 44, giữ VehicleBooking free-text (FK link defer). Caught gotcha #57 backlog (LeaveType/Shift unfiltered).

## Learnings

- **gotcha #57 NEW:** soft-delete + UNIQUE → MUST `.HasFilter("[IsDeleted]=0")`. Backlog: LeaveType.Code + ShiftPattern.Code vẫn unfiltered.
- **`feedback_background_spawn_visibility`:** spawn agent foreground = im lặng vài phút = "looks frozen" (anh phản hồi). → đẩy long work background + report "đã launch X" ngay. Cũng có 1 dead turn ("No response requested") = hiccup thật, em main nhận.
- **Test theo CODE = single source of truth** → test lòi drift thật (Holiday) → REPORT → fix proper. Closed loop: gap test → bug surfaced → fixed + verified prod cùng session.
- **EF model-metadata assertion** (`db.Model.FindEntityType(...).GetForeignKeys()...DeleteBehavior`) = cách lock schema intent (cascade) không cần DB round-trip — Pattern-10-style cho EF model.

## Next (S46, anh pick)
P11-C Vehicle+Driver (Mig 44, recon ready) · gotcha #57 fix LeaveType/Shift filtered-unique (gộp P11-C) · P11-D ItTicket SLA / P11-E AttendanceReport / P11-F MaTicket · Phase 9 Ops.
