[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:
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user