[CLAUDE] Tests: Plan C task 4 — regression test #44 silent 403 (Authorize policy ApprovalWorkflowsV2)
5 reflection-based tests verify ApprovalWorkflowsV2Controller Authorize policy split (gotcha #44 fix `f77ea38` S18): - class-level [Authorize] (any authenticated), NO Policy - GET Overview inherits class-level (no action policy) - POST Create + DELETE + PATCH user-selectable require Policy="Workflows.Create" Pattern reusable: catch future regression nếu ai add Policy lên class-level hoặc GET action → test fail ngay, không cần UAT reproduce silent 403. Add ProjectReference Api → Infrastructure.Tests cho reflection access. Verify: - dotnet test SolutionErp.slnx — 89/89 PASS (58 Domain + 31 Infra = 26+5 #44) Δ: 84 → 89 (+5) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,90 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using SolutionErp.Api.Controllers;
|
||||||
|
|
||||||
|
namespace SolutionErp.Infrastructure.Tests.Api;
|
||||||
|
|
||||||
|
// Regression tests for gotcha #44 — Silent 403 từ class-level [Authorize(Policy = ...)]
|
||||||
|
// quá strict (Session 18, fix `f77ea38`).
|
||||||
|
//
|
||||||
|
// Bug ngày 2026-05-08: ApprovalWorkflowsV2Controller class-level
|
||||||
|
// `[Authorize(Policy = "Workflows.Read")]` → Drafter `nv.test` (chỉ có
|
||||||
|
// `PurchaseEvaluations.Read`) bị 403 silent khi GET /api/approval-workflows-v2.
|
||||||
|
// FE TanStack Query catch silent → Workspace dropdown empty không có error toast.
|
||||||
|
//
|
||||||
|
// Fix: split policy per action — class-level [Authorize] (any authenticated)
|
||||||
|
// cho list-pick GET, action-level [Authorize(Policy = "Workflows.Create")]
|
||||||
|
// cho POST/DELETE/PATCH admin-only.
|
||||||
|
//
|
||||||
|
// Regression coverage: nếu future ai đó add Policy="Workflows.Read" lên
|
||||||
|
// class-level OR add Policy lên GET action → test FAIL ngay, không cần UAT
|
||||||
|
// reproduce lại bug silent 403 mà FE catch không hiển thị.
|
||||||
|
public class AuthorizePolicyRegressionTests
|
||||||
|
{
|
||||||
|
private static AuthorizeAttribute? GetClassLevelAuthorize(Type controllerType)
|
||||||
|
=> controllerType.GetCustomAttributes<AuthorizeAttribute>(inherit: false).FirstOrDefault();
|
||||||
|
|
||||||
|
private static AuthorizeAttribute? GetActionAuthorize(Type controllerType, string methodName)
|
||||||
|
=> controllerType
|
||||||
|
.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
|
||||||
|
.FirstOrDefault(m => m.Name == methodName)
|
||||||
|
?.GetCustomAttributes<AuthorizeAttribute>(inherit: false)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApprovalWorkflowsV2Controller_ClassLevel_AuthorizeOnly_NoPolicy()
|
||||||
|
{
|
||||||
|
var attr = GetClassLevelAuthorize(typeof(ApprovalWorkflowsV2Controller));
|
||||||
|
|
||||||
|
attr.Should().NotBeNull("controller phải có [Authorize] class-level để chặn anonymous");
|
||||||
|
attr!.Policy.Should().BeNull(
|
||||||
|
"class-level KHÔNG được hardcode Policy — sẽ block Drafter 403 silent khi GET. " +
|
||||||
|
"Gotcha #44: ràng buộc per-action thay vì class-level uniform.");
|
||||||
|
attr.Roles.Should().BeNull("class-level KHÔNG được hardcode Roles");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApprovalWorkflowsV2Controller_Overview_GET_InheritsClassLevel_NoActionPolicy()
|
||||||
|
{
|
||||||
|
// GET Overview KHÔNG được override với [Authorize(Policy=...)] — Drafter
|
||||||
|
// cần list workflow để pick lúc create PE/HĐ (read-only, không expose
|
||||||
|
// business data nhạy cảm).
|
||||||
|
var attr = GetActionAuthorize(typeof(ApprovalWorkflowsV2Controller), nameof(ApprovalWorkflowsV2Controller.Overview));
|
||||||
|
|
||||||
|
attr.Should().BeNull(
|
||||||
|
"GET Overview phải inherit class-level [Authorize] (any authenticated). " +
|
||||||
|
"Nếu add [Authorize(Policy=...)] action-level → Drafter 403 silent (gotcha #44).");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApprovalWorkflowsV2Controller_Create_POST_RequiresWorkflowsCreatePolicy()
|
||||||
|
{
|
||||||
|
// POST Create chỉ admin Designer được tạo workflow — phải có policy guard.
|
||||||
|
var attr = GetActionAuthorize(typeof(ApprovalWorkflowsV2Controller), nameof(ApprovalWorkflowsV2Controller.Create));
|
||||||
|
|
||||||
|
attr.Should().NotBeNull("POST Create phải có [Authorize(Policy = ...)] admin-only");
|
||||||
|
attr!.Policy.Should().Be("Workflows.Create",
|
||||||
|
"POST Create chỉ admin được tạo workflow mới (Mig 22 V2 Designer).");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApprovalWorkflowsV2Controller_Delete_RequiresWorkflowsCreatePolicy()
|
||||||
|
{
|
||||||
|
var attr = GetActionAuthorize(typeof(ApprovalWorkflowsV2Controller), nameof(ApprovalWorkflowsV2Controller.Delete));
|
||||||
|
|
||||||
|
attr.Should().NotBeNull("DELETE phải có [Authorize(Policy = ...)] admin-only");
|
||||||
|
attr!.Policy.Should().Be("Workflows.Create",
|
||||||
|
"DELETE workflow chỉ admin (Designer).");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ApprovalWorkflowsV2Controller_SetUserSelectable_PATCH_RequiresWorkflowsCreatePolicy()
|
||||||
|
{
|
||||||
|
// Mig 25 — admin toggle stick/unstick "cho user pick lúc create phiếu".
|
||||||
|
var attr = GetActionAuthorize(typeof(ApprovalWorkflowsV2Controller), nameof(ApprovalWorkflowsV2Controller.SetUserSelectable));
|
||||||
|
|
||||||
|
attr.Should().NotBeNull("PATCH user-selectable phải có [Authorize(Policy = ...)] admin-only");
|
||||||
|
attr!.Policy.Should().Be("Workflows.Create",
|
||||||
|
"PATCH user-selectable chỉ admin (Mig 25 Designer pin/unpin).");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Backend\SolutionErp.Infrastructure\SolutionErp.Infrastructure.csproj" />
|
<ProjectReference Include="..\..\src\Backend\SolutionErp.Infrastructure\SolutionErp.Infrastructure.csproj" />
|
||||||
|
<!-- Api reference cho reflection-based Authorize policy regression tests (gotcha #44) -->
|
||||||
|
<ProjectReference Include="..\..\src\Backend\SolutionErp.Api\SolutionErp.Api.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user