[CLAUDE] Tests: close 3 HRM coverage gaps (S45)

Gap1 (CRITICAL) Holiday composite UNIQUE (Year,Date): Create/Update guard, self-update no false-positive, soft-delete exclusion. Gap2 EmployeeSatellite: 5x FK-invariant parent guard + soft-delete + cascade semantics + EF model cascade config. Gap3 gotcha #44 authz regression: HrmConfigsController (bare [Authorize] + writes Roles=Admin) + EmployeesController (class Policy Hrm_HoSo.Read + per-action). 154 -> 181 PASS (+27, Infra 96->123).

Surfaced drift (test-locked current behavior, fix pending): Holiday DB UNIQUE (Year,Date) NOT filtered by IsDeleted -> recreating on soft-deleted slot throws DbUpdateException(500) vs app-level !IsDeleted intent. Inconsistent with PE/Contract LevelOpinions filtered-unique pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-06-01 13:38:05 +07:00
parent dbbed1534d
commit 051b62bc2f
3 changed files with 585 additions and 0 deletions

View File

@ -87,4 +87,117 @@ public class AuthorizePolicyRegressionTests
attr!.Policy.Should().Be("Workflows.Create",
"PATCH user-selectable chỉ admin (Mig 25 Designer pin/unpin).");
}
// ===================================================================
// Coverage gap #3 (MAJOR — S35/S36 backlog, closed S45 2026-06-01).
// gotcha #44 regression cho 2 controller HRM bị MISS (chỉ ApprovalWorkflowsV2 có trước đó).
//
// 2 controller shape KHÁC nhau — lock đúng intent (KHÔNG ép về cùng 1 kiểu):
// - HrmConfigsController: class [Authorize] trần (any-authenticated GET dropdown), writes Admin-role.
// - EmployeesController: class Policy-gated "Hrm_HoSo.Read" + per-action Create/Update/Delete policy.
// ===================================================================
// ---------- HrmConfigsController (class [Authorize] trần + writes Admin-role) ----------
[Fact]
public void HrmConfigsController_ClassLevel_AuthorizeOnly_NoPolicy_NoRoles()
{
// Class-level [Authorize] trần — GET (leave-types/holidays/shifts/ot-policies) phải mở cho
// MỌI authenticated user (dropdown master data). KHÔNG được hardcode Policy/Roles ở class-level
// → sẽ block role không-Admin 403 silent khi GET (gotcha #44).
var attr = GetClassLevelAuthorize(typeof(HrmConfigsController));
attr.Should().NotBeNull("controller phải có [Authorize] class-level chặn anonymous");
attr!.Policy.Should().BeNull("class-level KHÔNG được hardcode Policy — GET dropdown cho mọi authenticated");
attr.Roles.Should().BeNull("class-level KHÔNG được hardcode Roles — chỉ writes mới gắn Roles=Admin");
}
[Fact]
public void HrmConfigsController_ListHolidays_GET_InheritsClassLevel_NoActionAttr()
{
// GET list KHÔNG được override [Authorize(...)] action-level — mọi authenticated user
// cần đọc cho dropdown (gotcha #44 silent 403 prevention).
var attr = GetActionAuthorize(typeof(HrmConfigsController), nameof(HrmConfigsController.ListHolidays));
attr.Should().BeNull("GET ListHolidays phải inherit class-level [Authorize] (any authenticated)");
}
[Fact]
public void HrmConfigsController_CreateHoliday_POST_RequiresAdminRole()
{
var attr = GetActionAuthorize(typeof(HrmConfigsController), nameof(HrmConfigsController.CreateHoliday));
attr.Should().NotBeNull("POST CreateHoliday phải có [Authorize(Roles = ...)] write-guard");
attr!.Roles.Should().Be("Admin", "ghi Holiday chỉ Admin (role-based, KHÔNG policy).");
attr.Policy.Should().BeNull("HrmConfigs writes dùng Roles=Admin, KHÔNG dùng Policy.");
}
[Fact]
public void HrmConfigsController_UpdateHoliday_PUT_RequiresAdminRole()
{
var attr = GetActionAuthorize(typeof(HrmConfigsController), nameof(HrmConfigsController.UpdateHoliday));
attr.Should().NotBeNull("PUT UpdateHoliday phải có [Authorize(Roles = ...)] write-guard");
attr!.Roles.Should().Be("Admin");
}
[Fact]
public void HrmConfigsController_DeleteHoliday_DELETE_RequiresAdminRole()
{
var attr = GetActionAuthorize(typeof(HrmConfigsController), nameof(HrmConfigsController.DeleteHoliday));
attr.Should().NotBeNull("DELETE DeleteHoliday phải có [Authorize(Roles = ...)] write-guard");
attr!.Roles.Should().Be("Admin");
}
// ---------- EmployeesController (class Policy-gated + per-action policy) ----------
[Fact]
public void EmployeesController_ClassLevel_RequiresHrmHoSoReadPolicy()
{
// Class-level Policy "Hrm_HoSo.Read" — KHÁC HrmConfigs (hồ sơ NS nhạy cảm hơn dropdown).
// Lock intent: role thiếu Read sẽ 403 (FE PermissionGuard wrap để tránh silent UX — gotcha #44).
var attr = GetClassLevelAuthorize(typeof(EmployeesController));
attr.Should().NotBeNull("EmployeesController phải có [Authorize] class-level");
attr!.Policy.Should().Be("Hrm_HoSo.Read",
"hồ sơ nhân sự gate bằng policy Read class-level (KHÁC HrmConfigs any-authenticated).");
}
[Fact]
public void EmployeesController_Create_POST_RequiresHrmHoSoCreatePolicy()
{
var attr = GetActionAuthorize(typeof(EmployeesController), nameof(EmployeesController.Create));
attr.Should().NotBeNull("POST Create phải override action-level policy");
attr!.Policy.Should().Be("Hrm_HoSo.Create");
}
[Fact]
public void EmployeesController_Update_PUT_RequiresHrmHoSoUpdatePolicy()
{
var attr = GetActionAuthorize(typeof(EmployeesController), nameof(EmployeesController.Update));
attr.Should().NotBeNull("PUT Update phải override action-level policy");
attr!.Policy.Should().Be("Hrm_HoSo.Update");
}
[Fact]
public void EmployeesController_Delete_DELETE_RequiresHrmHoSoDeletePolicy()
{
var attr = GetActionAuthorize(typeof(EmployeesController), nameof(EmployeesController.Delete));
attr.Should().NotBeNull("DELETE Delete phải override action-level policy");
attr!.Policy.Should().Be("Hrm_HoSo.Delete");
}
[Fact]
public void EmployeesController_CreateWorkHistory_Satellite_RequiresHrmHoSoCreatePolicy()
{
// 1 satellite representative — satellite write inherit cùng Create policy như parent.
var attr = GetActionAuthorize(typeof(EmployeesController), nameof(EmployeesController.CreateWorkHistory));
attr.Should().NotBeNull("POST satellite CreateWorkHistory phải có action-level policy");
attr!.Policy.Should().Be("Hrm_HoSo.Create");
}
}