[CLAUDE] Infra: go nhanh S92 khoi revoker (owner supersede: hien thi het) + skeleton menu GD2/GD4 + ComingSoonPage x2
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m56s

- RevokeTemporarilyHiddenModulesAsync: GO Contracts+Ct_*/Master-set khoi predicate (bai tra gia: grant SQL tay bi revoker lat o restart #423 - 447/494 row roi; regrant=447 da chay truoc commit nay). Hrm/Off/Personal GIU (y owner: vung dang-lam chi admin)
- reviewKeys += ContractMenuKeys + MasterMenuKeys + KhkkKeys + HdcKeys (CanRead-only all-role, fresh-env hoi tu)
- Seeder tree.Add: Khkk_G1 + 4 leaves (khuon Duyet NCC) + Hdc_x7 (tai dung nhan 01.-07.) - prod da INSERT truc tiep (perm=156)
- ComingSoonPage mirror x2 (md5-identical) + route /coming-soon x2 + staticMap 11 leaf x2 (gotcha #50)
- Test: AdminOnlyModulesRevokeTests tach StillHidden/Reopened + REGRESSION-GUARD S159 (re-add nhanh S92 => do); ProcurementMasterAccessSeedTests doc lai isolation bang CO CAO (spec-change S92->S159, update test + code chung commit)
- 562 test PASS - nghiem thu cuoi = restart THAT khi deploy run nay (khuon S91)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-29 12:34:15 +07:00
parent b5a7dd8a92
commit 2a7269581c
9 changed files with 234 additions and 27 deletions

View File

@ -32,26 +32,37 @@ namespace SolutionErp.Infrastructure.Tests.Application;
// (Cascade) → PHẢI seed MenuItem rows + Role TRƯỚC khi seed Permission (nếu không SQLite FK Error 19).
public class AdminOnlyModulesRevokeTests
{
// Đại diện 5 nhóm anh chốt admin-only — sau revoke PHẢI CanRead=false (non-Admin).
// Dùng const/factory MenuKeys (KHÔNG hardcode string) để bắt đồng-bộ nếu key đổi.
private static readonly string[] S92HiddenKeys =
// [S159 2026-07-29] Owner SUPERSEDE S92: "đang phát triển → cho hiển thị hết để mọi
// người góp ý; Duyệt NCC để như cũ" ⇒ nhánh Contracts/Ct_*/Master-set GỠ khỏi revoker.
// Tập ẩn CÒN LẠI = 3 nhóm đang-làm (Hrm/Off/Personal — ý ❸ owner cùng ngày).
private static readonly string[] StillHiddenKeys =
{
MenuKeys.Hrm, // "Hrm" — match StartsWith("Hrm")
MenuKeys.Off, // "Off" — match StartsWith("Off")
MenuKeys.Personal, // "Personal" — match == Personal
};
// [S159] Bộ key S92 cũ nay MỞ LẠI — revoker KHÔNG ĐƯỢC chạm nữa. Giữ danh sách này
// làm REGRESSION-GUARD: bài trả giá 2026-07-29 = grant 38 key bằng SQL tay bị chính
// revoker lật ở app-restart kế (Run #423, 447/494 row rơi, 11/13 role về 0 —
// cicd-verify-1a47a61.md mục 9). Ai re-add nhánh S92 vào predicate ⇒ test này ĐỎ.
private static readonly string[] ReopenedKeys =
{
// HỢP ĐỒNG (Contracts + Ct_*)
MenuKeys.Contracts,
MenuKeys.ContractTypeGroup("ThauPhu"), // "Ct_ThauPhu" — match StartsWith("Ct_")
MenuKeys.ContractTypeGroup("ThauPhu"), // "Ct_ThauPhu"
// DANH MỤC (Master + 3 leaf + Catalog*)
MenuKeys.Master,
MenuKeys.Suppliers,
MenuKeys.Projects,
MenuKeys.Departments,
MenuKeys.Catalogs, // "Catalogs" — match StartsWith("Catalog")
MenuKeys.CatalogWorkItems, // "CatalogWorkItems" — match StartsWith("Catalog")
// Nhóm cũ S58 (regression — vẫn phải ẩn)
MenuKeys.Hrm, // "Hrm" — match StartsWith("Hrm")
MenuKeys.Off, // "Off" — match StartsWith("Off")
MenuKeys.Personal, // "Personal" — match == Personal
MenuKeys.Catalogs,
MenuKeys.CatalogWorkItems,
};
private static readonly string[] S92HiddenKeys =
StillHiddenKeys.Concat(ReopenedKeys).ToArray(); // giữ tên cho seed-helper cũ
// Pe_* (Duyệt NCC) + root PurchaseEvaluations — KHÔNG bị revoke (giữ cho user thường).
private static readonly string[] PeKeptKeys =
{
@ -130,15 +141,22 @@ public class AdminOnlyModulesRevokeTests
await InvokeRevokeAsync(fix);
foreach (var key in S92HiddenKeys)
// Vế 1 — 3 nhóm đang-làm VẪN bị revoke (ý ❸ giữ).
foreach (var key in StillHiddenKeys)
{
var row = await GetPermAsync(db, nonAdminId, key);
row.Should().NotBeNull($"{key} vẫn có row (revoke chỉ hạ cờ, KHÔNG xóa — giữ vết flip lại)");
row!.CanRead.Should().BeFalse($"{key} bị revoke CanRead — menu ẩn khỏi non-Admin (chỉ Admin thấy)");
row!.CanRead.Should().BeFalse($"{key} bị revoke CanRead — nhóm đang-làm ẩn khỏi non-Admin");
row.CanCreate.Should().BeFalse($"{key} revoke CanCreate");
row.CanUpdate.Should().BeFalse($"{key} revoke CanUpdate");
row.CanDelete.Should().BeFalse($"{key} revoke CanDelete");
}
// Vế 2 — ⭐ REGRESSION-GUARD S159: bộ key MỞ LẠI phải GIỮ NGUYÊN CanRead=true
// sau revoke (revoker không còn nhánh S92; re-add là test này đỏ ngay).
foreach (var key in ReopenedKeys)
(await CanReadAsync(db, nonAdminId, key)).Should().BeTrue(
$"{key} — owner S159 mở lại cho mọi role; revoker KHÔNG được lật grant này nữa");
}
// Admin loại trừ khỏi revoke → giữ nguyên CanRead=true (vẫn thấy 5 nhóm).

View File

@ -189,12 +189,14 @@ public class ProcurementMasterAccessSeedTests
await InvokeRevokeAsync(fix); // revoke CẢ HAI role về false
await InvokeSeedProcurementAsync(fix); // CHỈ nâng lại Procurement
// ⭐ ISOLATION: DeptManager row TỒN TẠI (revoke giữ vết) NHƯNG mọi cờ = false — seeder
// Procurement KHÔNG chạm role khác. Assert cả Master + Suppliers (2 key task chốt) + 7 key kia.
// ⭐ ISOLATION (đọc lại theo S159): revoker KHÔNG còn chạm Master-set ⇒ CanRead=true
// pre-grant của DeptManager GIỮ NGUYÊN (chính sách all-role read — owner 2026-07-29
// supersede S92). Isolation của Procurement-seed nay đo bằng CỜ CAO: seeder chỉ nâng
// CanCreate/Update cho Procurement, KHÔNG rò cờ cao sang role khác.
foreach (var key in AllGrantKeys)
await AssertFlagsAsync(db, otherId, key,
r: false, c: false, u: false, d: false,
"DeptManager KHÔNG được Procurement-grant chạm — S92 'chỉ Admin thấy' giữ nguyên role khác");
r: true, c: false, u: false, d: false,
"S159 all-role read GIỮ; cờ cao KHÔNG rò sang DeptManager từ Procurement-grant");
// CONTRAST (chống vacuous-pass): Procurement THẬT SỰ được nâng → chứng seeder CÓ chạy,
// nên isolation ở trên KHÔNG phải "false vì seeder no-op".
@ -246,10 +248,10 @@ public class ProcurementMasterAccessSeedTests
var procCount = await db.Permissions.AsNoTracking().CountAsync(p => p.RoleId == procurementId);
procCount.Should().Be(AllGrantKeys.Length, "idempotent — chạy lại KHÔNG tạo row trùng");
// Role khác vẫn bị ẩn sau lần 2 (isolation bền vững).
// Isolation bền vững sau lần 2 (S159: CanRead all-role GIỮ true; cờ cao vẫn false).
foreach (var key in AllGrantKeys)
await AssertFlagsAsync(db, otherId, key,
r: false, c: false, u: false, d: false,
"idempotent — lần seed thứ 2 KHÔNG rò sang DeptManager");
r: true, c: false, u: false, d: false,
"idempotent — lần seed thứ 2 KHÔNG rò CỜ CAO sang DeptManager (CanRead all-role S159 giữ)");
}
}