[CLAUDE] Docs+Tests: chốt final session 5 — 77 test (Phase 3 mini PE WF) + 3 gotcha CI + 8 doc updates
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m21s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m21s
Final close session 5 — bao gồm: ==== Tests Phase 3 mini (NEW) ==== tests/SolutionErp.Infrastructure.Tests/Application/PeWorkflowAdminTests.cs - 6 test CreatePeWorkflowDefinitionCommandHandler: - First version → IsActive=true, Version=1, ActivatedAt set - Second version same Code → auto-increment v2 + deactivate v1 (atomic) - Different EvaluationType (A vs B) → independent active state - Persists steps ordered by Order field - Persists approvers per step - Third version → v1 + v2 deactivate, v3 active Total tests: 71 → 77 pass / ~2s (54 Domain + 23 Infra). Skip Phase 3 full (UpsertOpinion + Budget link validation) — cần Identity UserManager DI helper, defer session sau. ==== 3 gotcha CI mới (#39 #40 #41) ==== - #39 act_runner github.com TCP timeout 21s → manual checkout fix (run #108/#109 fail, #110 pass) - #40 npm junction cache `tsc not found` after Move-Item — rolled back, hypothesis nested junctions trong node_modules disrupt .bin/ paths. TODO debug session sau với robocopy hoặc act_runner cache.host - #41 Gitea Actions paths-ignore behavior — workflow file change vẫn trigger (correct), commit MD-only skip 100% (verify512880c→ no run #113) + Checklist debug bug mới items 18-20 referencing 3 gotcha trên. ==== Doc updates (8 file) ==== - STATUS.md: header Phase 8 update + 3 row Recently Done CI fixes + cumulative test 71→77 - HANDOFF.md: TL;DR + CI optimize section + Phase status + gotcha count 38→41 - migration-todos.md: Phase 8 §E updated với Phase 3 mini done + CI fixes - rules.md §7 Testing: rewrite full — stack + test pyramid + quy tắc bổ sung mỗi feature + workflow user end-of-task (`dotnet test` local trước push) + CI gate behavior - architecture.md §11: update test pyramid + phased priority + CI optimization sub-section (3 fix manual checkout / path filter / npm cache rollback) + tốc độ deploy table - gotchas.md: + #39 #40 #41 đầy đủ (triệu chứng + nguyên nhân + fix + reference) - ef-core-migration SKILL: Phase 8 update note thêm CI fixes + 77 test - CLAUDE.md root: test count 71→77 + folder structure + CI/CD pipeline 3 fix section - memory project_solution_erp.md: session 5 summary + workflow user mới - session log 2026-04-29-2300-chot-final-ci-tests-gotchas.md (NEW — 9 section detail) ==== Skill audit cron ==== `solution-erp-skill-audit-monthly` next fire 2026-05-01 (2 ngày sau). Cron survives across sessions (setup commitb904a25). Khi fire sẽ: - Cross-check 6 skill với STATUS/gotchas/migration-todos - Auto-refresh stale + đề xuất add/archive cho human approve - Log vào docs/changelog/skill-audit-2026-05.md - ABORT nếu repo dirty ==== Verify ==== - dotnet test SolutionErp.slnx → 77 pass / ~2s (54 Domain + 23 Infra) - git status clean sau commit này - CI: commit này chứa code (test + workflow) → trigger CI test gate Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -0,0 +1,167 @@
|
||||
using SolutionErp.Application.PurchaseEvaluations;
|
||||
using SolutionErp.Domain.Contracts; // WorkflowApproverKind reuse
|
||||
using SolutionErp.Domain.Identity;
|
||||
using SolutionErp.Domain.PurchaseEvaluations;
|
||||
using SolutionErp.Infrastructure.Tests.Common;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Tests.Application;
|
||||
|
||||
// Tests cho PeWorkflowAdminFeatures (PE Workflow Designer admin UI — session 5).
|
||||
// Mục tiêu: chống regression Versioning logic (auto-increment + deactivate cũ),
|
||||
// vì invariant "AT MOST ONE IsActive=true per EvaluationType" rất quan trọng.
|
||||
public class CreatePeWorkflowDefinitionCommandHandlerTests
|
||||
{
|
||||
private static CreatePeWorkflowDefinitionCommand BuildCmd(
|
||||
PurchaseEvaluationType type = PurchaseEvaluationType.DuyetNcc,
|
||||
string code = "QT-DN-A-TEST",
|
||||
string name = "Test Workflow")
|
||||
{
|
||||
return new CreatePeWorkflowDefinitionCommand(
|
||||
EvaluationType: type,
|
||||
Code: code,
|
||||
Name: name,
|
||||
Description: null,
|
||||
Steps: new List<CreatePeWorkflowStepInput>
|
||||
{
|
||||
new(Order: 1, Phase: (int)PurchaseEvaluationPhase.DangSoanThao, Name: "Soạn", SlaDays: 3,
|
||||
Approvers: new List<CreatePeWorkflowStepApproverInput>
|
||||
{
|
||||
new(Kind: (int)WorkflowApproverKind.Role, AssignmentValue: AppRoles.Drafter),
|
||||
}),
|
||||
new(Order: 2, Phase: (int)PurchaseEvaluationPhase.ChoCCM, Name: "CCM duyệt", SlaDays: 2,
|
||||
Approvers: new List<CreatePeWorkflowStepApproverInput>
|
||||
{
|
||||
new(Kind: (int)WorkflowApproverKind.Role, AssignmentValue: AppRoles.CostControl),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_FirstVersion_PersistsWith_Version1_And_IsActiveTrue()
|
||||
{
|
||||
using var fix = new SqliteDbFixture();
|
||||
var handler = new CreatePeWorkflowDefinitionCommandHandler(fix.Db);
|
||||
|
||||
var id = await handler.Handle(BuildCmd(), CancellationToken.None);
|
||||
|
||||
var def = fix.Db.PurchaseEvaluationWorkflowDefinitions
|
||||
.Where(d => d.Id == id)
|
||||
.Single();
|
||||
def.Version.Should().Be(1);
|
||||
def.IsActive.Should().BeTrue();
|
||||
def.ActivatedAt.Should().NotBeNull();
|
||||
def.Code.Should().Be("QT-DN-A-TEST");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_SecondVersionSameCode_AutoIncrements_AndDeactivatesPrevious()
|
||||
{
|
||||
using var fix = new SqliteDbFixture();
|
||||
var handler = new CreatePeWorkflowDefinitionCommandHandler(fix.Db);
|
||||
|
||||
var id1 = await handler.Handle(BuildCmd(name: "v1"), CancellationToken.None);
|
||||
var id2 = await handler.Handle(BuildCmd(name: "v2"), CancellationToken.None);
|
||||
|
||||
var def1 = fix.Db.PurchaseEvaluationWorkflowDefinitions.Single(d => d.Id == id1);
|
||||
var def2 = fix.Db.PurchaseEvaluationWorkflowDefinitions.Single(d => d.Id == id2);
|
||||
|
||||
def1.IsActive.Should().BeFalse("Version cũ phải bị deactivate khi tạo version mới");
|
||||
def1.Version.Should().Be(1);
|
||||
|
||||
def2.IsActive.Should().BeTrue();
|
||||
def2.Version.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_DifferentEvaluationType_DoesNotAffect_Other()
|
||||
{
|
||||
using var fix = new SqliteDbFixture();
|
||||
var handler = new CreatePeWorkflowDefinitionCommandHandler(fix.Db);
|
||||
|
||||
// Type A active
|
||||
var idA = await handler.Handle(
|
||||
BuildCmd(type: PurchaseEvaluationType.DuyetNcc, code: "QT-DN-A"),
|
||||
CancellationToken.None);
|
||||
|
||||
// Type B active — không được tắt Type A
|
||||
var idB = await handler.Handle(
|
||||
BuildCmd(type: PurchaseEvaluationType.DuyetNccPhuongAn, code: "QT-DN-B"),
|
||||
CancellationToken.None);
|
||||
|
||||
var defA = fix.Db.PurchaseEvaluationWorkflowDefinitions.Single(d => d.Id == idA);
|
||||
var defB = fix.Db.PurchaseEvaluationWorkflowDefinitions.Single(d => d.Id == idB);
|
||||
|
||||
defA.IsActive.Should().BeTrue("Type A không liên quan tới Type B");
|
||||
defB.IsActive.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_PersistsAllSteps_OrderedByOrderField()
|
||||
{
|
||||
using var fix = new SqliteDbFixture();
|
||||
var handler = new CreatePeWorkflowDefinitionCommandHandler(fix.Db);
|
||||
|
||||
var id = await handler.Handle(BuildCmd(), CancellationToken.None);
|
||||
|
||||
var steps = fix.Db.PurchaseEvaluationWorkflowSteps
|
||||
.Where(s => s.PurchaseEvaluationWorkflowDefinitionId == id)
|
||||
.OrderBy(s => s.Order)
|
||||
.ToList();
|
||||
|
||||
steps.Should().HaveCount(2);
|
||||
steps[0].Order.Should().Be(1);
|
||||
steps[0].Phase.Should().Be(PurchaseEvaluationPhase.DangSoanThao);
|
||||
steps[0].Name.Should().Be("Soạn");
|
||||
steps[1].Order.Should().Be(2);
|
||||
steps[1].Phase.Should().Be(PurchaseEvaluationPhase.ChoCCM);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_PersistsApprovers_PerStep()
|
||||
{
|
||||
using var fix = new SqliteDbFixture();
|
||||
var handler = new CreatePeWorkflowDefinitionCommandHandler(fix.Db);
|
||||
|
||||
var id = await handler.Handle(BuildCmd(), CancellationToken.None);
|
||||
|
||||
var stepIds = fix.Db.PurchaseEvaluationWorkflowSteps
|
||||
.Where(s => s.PurchaseEvaluationWorkflowDefinitionId == id)
|
||||
.Select(s => s.Id)
|
||||
.ToList();
|
||||
var approvers = fix.Db.PurchaseEvaluationWorkflowStepApprovers
|
||||
.Where(a => stepIds.Contains(a.PurchaseEvaluationWorkflowStepId))
|
||||
.ToList();
|
||||
|
||||
approvers.Should().HaveCount(2);
|
||||
approvers.Should().Contain(a => a.Kind == WorkflowApproverKind.Role
|
||||
&& a.AssignmentValue == AppRoles.Drafter);
|
||||
approvers.Should().Contain(a => a.Kind == WorkflowApproverKind.Role
|
||||
&& a.AssignmentValue == AppRoles.CostControl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_ThirdVersion_DeactivatesV2_Increments_To_V3()
|
||||
{
|
||||
// Tránh edge case: deactivate logic có gặp v2 active không, hay vẫn xét v1?
|
||||
using var fix = new SqliteDbFixture();
|
||||
var handler = new CreatePeWorkflowDefinitionCommandHandler(fix.Db);
|
||||
|
||||
await handler.Handle(BuildCmd(name: "v1"), CancellationToken.None);
|
||||
await handler.Handle(BuildCmd(name: "v2"), CancellationToken.None);
|
||||
var id3 = await handler.Handle(BuildCmd(name: "v3"), CancellationToken.None);
|
||||
|
||||
var allDefs = fix.Db.PurchaseEvaluationWorkflowDefinitions
|
||||
.Where(d => d.Code == "QT-DN-A-TEST")
|
||||
.OrderBy(d => d.Version)
|
||||
.ToList();
|
||||
|
||||
allDefs.Should().HaveCount(3);
|
||||
allDefs[0].Version.Should().Be(1);
|
||||
allDefs[0].IsActive.Should().BeFalse();
|
||||
allDefs[1].Version.Should().Be(2);
|
||||
allDefs[1].IsActive.Should().BeFalse();
|
||||
allDefs[2].Version.Should().Be(3);
|
||||
allDefs[2].IsActive.Should().BeTrue();
|
||||
allDefs[2].Id.Should().Be(id3);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user