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