[CLAUDE] Workflow: sua quy trinh duyet TAI CHO + co F6 AllowApproverDelete (dot 1)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m31s

Nen cho tinh nang "xoa phieu PE o man duyet" (UAT: phieu bam sai goi thau dang
an luy ke). Dot 1 lam NEN, dot 2 moi lam nut xoa.

Vi sao phai lam nen truoc: phieu dang chay pin ApprovalWorkflowId va doc workflow
SONG theo Id do. Moi POST tao workflow moi = Id MOI, nen tick co tren ban moi
KHONG BAO GIO toi duoc phieu dang treo. Phai co lenh sua-tai-cho giu nguyen Id.

- Domain: ApprovalWorkflowLevel + AllowApproverDelete (F6, per-NGUOI) + Mig
  20260727033522_AddPeAllowApproverDelete (AddColumn bit default false, no new table)
- Application: UpdateAwDefinitionCommand + Validator + Handler
  - 2 phep thu: (T1) khung khong doi - Order tung row bat bien
                (T2) luat choi khong doi - cam CeoApprovalThreshold, AllowApproverFinalize,
                     Code, ApplicableType => 409 bao tao ban moi
  - diff theo Level.Id (CAM delete-then-insert: no FK Restrict + mo coi LevelOpinion)
  - guard 2 tang chu ky: tang 1 chu ky tren ho so CON SONG; tang 2 chu ky CON LUU
    (cha xoa mem hoac opinion xoa mem) - ca hai deu CHAN, khong xoa cung
  - purge chi khi ho so cha KHONG CON ROW; boc transaction (ExecuteDelete tu commit)
  - KHONG tang Version, KHONG dung IsActive, KHONG doi Id
- Api: PUT /api/approval-workflows-v2/{id} + [Authorize(Policy="Workflows.Create")]
- Infra: message 403 in TEN NGUOI thay GUID tran
- FE: Designer tach "Sua tai cho" (PUT) vs "Tao phien ban moi" (POST) + o tick F6
- Tests: +21 (551 tong, 0 fail) - gom F12 chung minh phieu dang chay nhan thay doi ngay

Review: 14 FLAG, 3 HIGH da va (ExecuteDelete ngoai transaction / purge xoa cung
chu ky phieu xoa mem / endpoint khong co day FE goi toi).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-27 12:36:59 +07:00
parent d745fec445
commit b1bae77c9c
13 changed files with 8604 additions and 41 deletions

View File

@ -1,6 +1,7 @@
using System.Reflection;
using Microsoft.AspNetCore.Authorization;
using SolutionErp.Api.Controllers;
using SolutionErp.Domain.Identity; // MenuKeys — chốt policy name có thật (F-1b S155)
namespace SolutionErp.Infrastructure.Tests.Api;
@ -88,6 +89,59 @@ public class AuthorizePolicyRegressionTests
"PATCH user-selectable chỉ admin (Mig 25 Designer pin/unpin).");
}
// ===================================================================
// F-1b (S155 2026-07-27) — PUT sửa quy trình TẠI CHỖ.
//
// 🔴 Nghiêm trọng hơn một endpoint ghi thường, vì CẢ HAI thao tác mà lệnh này
// cho phép đều là hành vi CẤP QUYỀN:
// (i) "thêm người vào Cấp" ⇒ tự thêm mình vào Cấp đang chờ ⇒ lọt guard
// match-approver ⇒ DUYỆT ĐƯỢC PHIẾU NGƯỜI KHÁC;
// (ii) "bật cờ Allow*" ⇒ gồm chính F6 AllowApproverDelete ⇒ TỰ CẤP
// QUYỀN XOÁ phiếu đang duyệt.
// Class-level chỉ [Authorize] trần (cố ý, gotcha #44) nên nó KHÔNG tự bảo vệ:
// thiếu đúng 1 dòng attribute = leo thang đặc quyền cho MỌI tài khoản đăng nhập.
// ===================================================================
[Fact]
public void ApprovalWorkflowsV2Controller_Update_PUT_RequiresWorkflowsCreatePolicy()
{
var attr = GetActionAuthorize(typeof(ApprovalWorkflowsV2Controller), nameof(ApprovalWorkflowsV2Controller.Update));
attr.Should().NotBeNull(
"PUT sửa-tại-chỗ phải có [Authorize(Policy = ...)] — class-level [Authorize] trần cho lọt mọi user đăng nhập");
attr!.Policy.Should().Be("Workflows.Create",
"khớp cả 3 verb ghi còn lại (POST/PATCH/DELETE) của chính controller này — sửa quy trình = quyền admin Designer.");
}
// Bẫy đi kèm (spec §B3): `Program.cs` CHỈ đăng ký policy `{menu}.{action}` cho
// menu key nằm trong `MenuKeys.All`. Gắn một policy KHÔNG được đăng ký thì
// ASP.NET ném lúc chạy — test attribute ở trên vẫn xanh mà endpoint vẫn hỏng.
// ⇒ chốt luôn rằng tên policy đang dùng là tên CÓ THẬT trong bộ sinh policy.
[Fact]
public void ApprovalWorkflowsV2Controller_WriteVerbs_PolicyName_IsActuallyRegisterable()
{
var writeActions = new[]
{
nameof(ApprovalWorkflowsV2Controller.Create),
nameof(ApprovalWorkflowsV2Controller.Update),
nameof(ApprovalWorkflowsV2Controller.SetUserSelectable),
nameof(ApprovalWorkflowsV2Controller.Delete),
};
foreach (var action in writeActions)
{
var policy = GetActionAuthorize(typeof(ApprovalWorkflowsV2Controller), action)?.Policy;
policy.Should().NotBeNullOrEmpty($"action {action} phải có policy");
var parts = policy!.Split('.');
parts.Should().HaveCount(2, $"policy {policy} phải đúng dạng {{menuKey}}.{{action}}");
MenuKeys.All.Should().Contain(parts[0],
$"policy \"{policy}\" của {action} trỏ menu key KHÔNG có trong MenuKeys.All ⇒ Program.cs không sinh policy đó ⇒ 500 lúc chạy (bẫy Pe_* trong spec S155 §B3)");
MenuKeys.Actions.Should().Contain(parts[1],
$"policy \"{policy}\" của {action} dùng action không có trong MenuKeys.Actions");
}
}
// ===================================================================
// 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 đó).