[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

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% (verify 512880c → 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 commit b904a25). 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:
pqhuy1987
2026-04-29 23:43:42 +07:00
parent 512880c5cb
commit b874743081
10 changed files with 650 additions and 35 deletions

View File

@ -48,8 +48,12 @@ Total: **52 bảng** dbo + `__EFMigrationsHistory`. Xem `docs/database/schema-di
**Phase 8 update (2026-04-29 Session 5):** **Phase 8 update (2026-04-29 Session 5):**
- Migration 15 `AddPurchaseEvaluationDepartmentOpinions` — 1 bảng riêng (UNIQUE PEId+Kind), max 4 row mỗi phiếu cho 4 phòng ban (Phê duyệt/Ccm/MuaHang/SmPm). UPDATE in-place khi user đổi ý, audit qua Changelog. - Migration 15 `AddPurchaseEvaluationDepartmentOpinions` — 1 bảng riêng (UNIQUE PEId+Kind), max 4 row mỗi phiếu cho 4 phòng ban (Phê duyệt/Ccm/MuaHang/SmPm). UPDATE in-place khi user đổi ý, audit qua Changelog.
- Tests Phase 1-2 + CI gate live: `tests/SolutionErp.Domain.Tests/` (54 test policy state machine) + `tests/SolutionErp.Infrastructure.Tests/` (17 test code generator). Total 71 test pass / ~2s. CI fail → no deploy. - Tests Phase 1-2-3mini live: `tests/SolutionErp.Domain.Tests/` (54 test policy state machine) + `tests/SolutionErp.Infrastructure.Tests/` (17 test code generator + 6 test PE WF versioning). **Total 77 test pass / ~3s**. CI fail → no deploy.
- Migration verify pattern thêm: chạy `dotnet test` local trước mỗi migration commit để chống regression policy/codegen. - CI optimize 3 fix (29/04):
- Manual checkout bypass github.com (gotcha #39) — fix TCP timeout 21s
- Path filter docs-only skip (gotcha #41) — commit MD-only KHÔNG trigger CI
- npm junction cache (gotcha #40) — rolled back, debug session sau
- Migration verify pattern thêm: chạy `dotnet test SolutionErp.slnx` local trước mỗi migration commit để chống regression policy/codegen.
**Phase 7 update (2026-04-28):** **Phase 7 update (2026-04-28):**
- Migration 14 `AddBudgets` — 4 bảng (Budgets/Details/Approvals/Changelogs) + 2 cột `BudgetId?` thêm vào Contracts & PurchaseEvaluations. Workflow hardcoded `BudgetPolicy.Default` (chưa versioned). - Migration 14 `AddBudgets` — 4 bảng (Budgets/Details/Approvals/Changelogs) + 2 cột `BudgetId?` thêm vào Contracts & PurchaseEvaluations. Workflow hardcoded `BudgetPolicy.Default` (chưa versioned).

View File

@ -63,7 +63,7 @@ Kiến trúc: **.NET 10 Clean Architecture + 2 React FE (admin + user) + SQL Ser
| Identity (User/Role/Permission/MenuItem) | `Domain/Identity/` | 1, 3, 11 | Feature-complete (30 demo user — 16 sample + 14 Solutions thật) | | Identity (User/Role/Permission/MenuItem) | `Domain/Identity/` | 1, 3, 11 | Feature-complete (30 demo user — 16 sample + 14 Solutions thật) |
| Forms (Template + Clause) | `Domain/Forms/` | 4 | Feature-complete | | Forms (Template + Clause) | `Domain/Forms/` | 4 | Feature-complete |
| Notifications | `Domain/Notifications/` | 6 | In-app + SignalR OK, email SMTP TODO | | Notifications | `Domain/Notifications/` | 6 | In-app + SignalR OK, email SMTP TODO |
| **Tests** | `tests/SolutionErp.{Domain,Infrastructure}.Tests/` | — | **71 test pass** (Phase 1: 54 Domain policy + Phase 2: 17 Infra code generator) — CI gate live | | **Tests** | `tests/SolutionErp.{Domain,Infrastructure}.Tests/` | — | **77 test pass** (54 Domain + 17 Infra + 6 PE WF Application Phase 3 mini) — CI gate + path filter docs-only skip |
### Commit convention ### Commit convention
@ -77,17 +77,27 @@ Kiến trúc: **.NET 10 Clean Architecture + 2 React FE (admin + user) + SQL Ser
``` ```
tests/ tests/
├── SolutionErp.Domain.Tests/ (54 test policy state machine - Phase 1) ├── SolutionErp.Domain.Tests/ (54 test - Phase 1: WorkflowPolicy / PEPolicy / BudgetPolicy)
└── SolutionErp.Infrastructure.Tests/ (17 test code generator format/sequence - Phase 2) └── SolutionErp.Infrastructure.Tests/ (17 + 6 = 23 test)
├── Services/ (17 test - Phase 2: Contract + PE Code Generator)
└── Application/ (6 test - Phase 3 mini: PeWorkflowDefinition versioning)
``` ```
**71 unit test pass** / ~2s. CI gate `.gitea/workflows/deploy.yml``dotnet test` fail → no deploy. **77 unit test pass** / ~3s. CI gate + path filter live.
```bash ```bash
dotnet test SolutionErp.slnx # chạy cả 2 test project dotnet test SolutionErp.slnx # chạy cả 2 test project
``` ```
**Quy tắc:** mỗi feature mới có guard logic / business rule → thêm test trước khi commit. Bug found in production → 1 regression test added before merge. Detail xem `docs/architecture.md §11`. **Quy tắc:** mỗi feature mới có guard logic / business rule → thêm test trước khi commit. Bug found in production → 1 regression test added before merge. Detail xem `docs/architecture.md §11` + `docs/rules.md §7`.
### CI/CD pipeline (3 fix lớn 29/04)
- ✅ Manual checkout bypass github.com (fix gotcha #39 TCP timeout)
- ✅ Path filter docs-only skip — `paths-ignore` (gotcha #41)
- ⏸️ npm cache (gotcha #40 — failed, rolled back)
**Tốc độ:** code commit ~3 phút / docs-only commit **0s** (skip).
## 🛠️ Skills (.claude/skills/) — 6 skill PHẢI dùng khi task khớp ## 🛠️ Skills (.claude/skills/) — 6 skill PHẢI dùng khi task khớp

View File

@ -1,6 +1,6 @@
# HANDOFF — Brief 5 phút cho session tiếp theo # HANDOFF — Brief 5 phút cho session tiếp theo
**Last updated:** 2026-04-29 (Phase 8 — **Budget FE complete + PE/HD-Budget integration + PE Workflow Designer + Ý kiến 4 PB + Tests Phase 1-2 + CI gate**) **Last updated:** 2026-04-29 tối (Phase 8 — **Budget complete + PE feature gap đóng + Tests Phase 1-2-3mini (77 test) + CI gate + Path filter + 3 gotcha CI mới (#39 #40 #41)**)
## TL;DR ## TL;DR
@ -34,8 +34,15 @@
- Pwd `User@123456`. Reconcile pattern (gotcha #38 4-field rename). - Pwd `User@123456`. Reconcile pattern (gotcha #38 4-field rename).
- Tổng 30 user (16 sample cũ giữ + 14 Solutions thật mới). - Tổng 30 user (16 sample cũ giữ + 14 Solutions thật mới).
**Tổng cumulative:** 52 DB tables, ~128 endpoints, 15 migrations, 38 gotchas, **Tổng cumulative:** 52 DB tables, ~128 endpoints, 15 migrations, **41 gotchas (+3 CI fixes)**,
**71 unit test**, 6 commit session 5 push lên Gitea. **77 unit test (+6 PE WF Application)**, 11+ commit session 5 push lên Gitea.
### 🆕 CI/CD optimize (29/04 tối)
-**Manual checkout bypass github.com** — fix #108/#109 transient TCP timeout 21s. Run #110 pass.
-**Path filter docs-only skip**`paths-ignore` trong on:push, commit MD-only KHÔNG trigger CI (verify `512880c` → no run #113).
- ⏸️ **npm junction cache** — thử ở #111 fail `tsc not found`, rollback. Cần debug session sau (gotcha #40 doc rồi).
-**Tests Phase 3 mini (PE Workflow Designer)** — 6 test versioning logic. Total 77.
## ⚠️ CẢNH BÁO session tiếp (Session 6) ## ⚠️ CẢNH BÁO session tiếp (Session 6)
@ -98,8 +105,12 @@
| **PE Workflow designer admin UI** `/system/pe-workflows/:typeCode` | ✅ Done (S5) | | **PE Workflow designer admin UI** `/system/pe-workflows/:typeCode` | ✅ Done (S5) |
| **Ý kiến 4 phòng ban PE** — migration 15 + section sign-off 2x2 grid | ✅ Done (S5) | | **Ý kiến 4 phòng ban PE** — migration 15 + section sign-off 2x2 grid | ✅ Done (S5) |
| **Tests Phase 1-2 + CI gate** — 71 test (Domain policy + Infra code generator) | ✅ Done (S5) | | **Tests Phase 1-2 + CI gate** — 71 test (Domain policy + Infra code generator) | ✅ Done (S5) |
| **CI manual checkout bypass github.com + Path filter docs-only skip** | ✅ Done (S5) |
| **Tests Phase 3 mini** — 6 test PE Workflow Designer versioning (total 77) | ✅ Done (S5) |
| **Export phiếu PDF/Excel PE**`IDocumentConverter` + template | 📝 Pending (không quan trọng) | | **Export phiếu PDF/Excel PE**`IDocumentConverter` + template | 📝 Pending (không quan trọng) |
| **Tests Phase 3-5** — Application handlers + API smoke + FE Vitest | 📝 Pending (làm khi cần) | | **Tests Phase 3 full** — Application handlers (UpsertOpinion + Budget link validation, cần UserManager setup) | 📝 Pending |
| **Tests Phase 4-5** — API smoke + FE Vitest | 📝 Pending (làm khi cần) |
| **npm cache CI optimize** (debug junction Move-Item issue #40) | 📝 Pending |
| 9+ Post-launch (E-signature, Bravo/SAP, Mobile, AI) | 📝 Future | | 9+ Post-launch (E-signature, Bravo/SAP, Mobile, AI) | 📝 Future |
## Run nhanh ## Run nhanh
@ -233,7 +244,7 @@ Trigger: user nói "audit skill" hoặc tự chạy đầu Phase mới.
## Lưu ý kỹ thuật quan trọng ## Lưu ý kỹ thuật quan trọng
**Đọc [`gotchas.md`](gotchas.md) (38 bẫy) trước khi:** **Đọc [`gotchas.md`](gotchas.md) (41 bẫy) trước khi:**
- Thêm package mới → .NET 10 compat (MediatR 14 fail → dùng 12.4.1) - Thêm package mới → .NET 10 compat (MediatR 14 fail → dùng 12.4.1)
- Debug TS enum error → dùng const-object pattern (`erasableSyntaxOnly`) - Debug TS enum error → dùng const-object pattern (`erasableSyntaxOnly`)

View File

@ -2,9 +2,9 @@
> **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`. > **Update rule:** trước khi bắt đầu 1 task → ghi row vào `🔥 In Progress`. Xong → chuyển sang `✅ Recently Done`.
**Last updated:** 2026-04-29 chiều (Phase 8 — **Budget FE complete + PE feature gap đóng + Tests Phase 1-2 + CI gate + Path filter docs-only skip**) **Last updated:** 2026-04-29 tối (Phase 8 — **Budget complete + PE feature gap đóng + Tests Phase 1-2-3mini + CI gate + Path filter + 3 gotcha CI mới**)
## 📍 Phase hiện tại: **Budget feature-complete + PE feature gap đóng + Test gate live** — 52 DB tables (+1 PEDepartmentOpinions), 15 migrations (+`AddPurchaseEvaluationDepartmentOpinions`), ~128 API endpoints (+4 PE WF admin + Opinion). 31 FE pages (+5 Budget + 1 PeWorkflowsPage). **71 unit test pass** (Phase 1: 54 Domain policy + Phase 2: 17 Infra code generator) — CI fail-fast nếu test fail. 30 demo user (16 sample + 14 Solutions thật). ## 📍 Phase hiện tại: **Feature freeze + CI optimize live** — 52 DB tables, 15 migrations, ~128 API endpoints, 31 FE pages. **77 unit test pass** (54 Domain policy + 17 Infra code generator + **6 PE Workflow versioning**) — CI fail-fast. Path filter docs-only skip = 0s cho commit MD. Manual checkout từ Gitea bypass github.com timeout (gotcha #39). 41 gotcha tổng. 30 demo user.
### 🌐 Production URLs ### 🌐 Production URLs
@ -48,6 +48,9 @@
| Ngày | Ai | Task | Commit | | Ngày | Ai | Task | Commit |
|---|---|---|---| |---|---|---|---|
| 2026-04-29 | Claude | **Tests Phase 3 mini + 3 gotcha CI mới (#39 #40 #41)**`tests/.../Application/PeWorkflowAdminTests.cs` 6 test versioning logic (CreatePeWorkflowDefinition: first version IsActive=true, second deactivates first, different EvaluationType independent, persists steps ordered + approvers per step, third version increments to v3). Total **77 test** (54 Domain + 17 Infra + 6 PE WF Application). Gotcha #39 act_runner github.com TCP timeout 21s + manual checkout fix. #40 npm junction cache fail `tsc not found` rolled back. #41 paths-ignore behavior + workflow file exclusion. | (cur commit) |
| 2026-04-29 | Claude | **CI Path filter docs-only skip live**`paths-ignore` trong on:push lookup `docs/**`/`**/*.md`/`.claude/skills/**`/`.gitignore`. Commit chỉ touch docs SKIP CI hoàn toàn (saving ~196s/commit, ~30% commit thuộc loại này). Verify `512880c` (docs-only) → Gitea NO trigger run #113. | `29eb5d9` · `a21790d` · `512880c` |
| 2026-04-29 | Claude | **CI manual checkout bypass github.com (fix #108/#109)** — Run #108/#109 fail TCP timeout 21s khi act_runner fetch `actions/checkout@v4` từ github.com. Replace `uses: actions/checkout@v4` + `actions/upload-artifact@v4` bằng manual `git init` + `git fetch` từ Gitea internal. Token `${{ github.token }}` auth tự sẵn per-job. Fetch by ref + depth=30. Run #110 pass 3m16s. | `14b7d18` · `26075c4` |
| 2026-04-29 | Claude | **Tests Phase 2 — Code generator format + sequence (SQLite in-memory)**`tests/SolutionErp.Infrastructure.Tests/` xUnit + EF SQLite 10. `SqliteDbFixture` + `TestApplicationDbContext` subclass override `nvarchar(max) → TEXT` (SQLite không support `max`). 17 test: ContractCodeGenerator (format RG-001 5 type + Framework year scope vs Project scope + sequence per prefix + year boundary reset + persistence verify) + PurchaseEvaluationCodeGenerator (format A/B + 3-digit pad + independent A/B sequences + year boundary). CI gate +1 step. Total 71 test pass / 2.1s. | `df5988b` | | 2026-04-29 | Claude | **Tests Phase 2 — Code generator format + sequence (SQLite in-memory)**`tests/SolutionErp.Infrastructure.Tests/` xUnit + EF SQLite 10. `SqliteDbFixture` + `TestApplicationDbContext` subclass override `nvarchar(max) → TEXT` (SQLite không support `max`). 17 test: ContractCodeGenerator (format RG-001 5 type + Framework year scope vs Project scope + sequence per prefix + year boundary reset + persistence verify) + PurchaseEvaluationCodeGenerator (format A/B + 3-digit pad + independent A/B sequences + year boundary). CI gate +1 step. Total 71 test pass / 2.1s. | `df5988b` |
| 2026-04-29 | Claude | **Tests Phase 1 — Domain unit tests + CI gate**`tests/SolutionErp.Domain.Tests/` xUnit 2.9 + FluentAssertions 7.2 (pin trước v8 commercial). 54 test pure function (no DB/IO): WorkflowPolicy (Standard 9-phase + SkipCcm 7-phase + Registry per ContractType + FromDefinition versioned + UserKindApprover) / PEPolicy (NccOnly 3-step + NccWithPlan 5-step + reject paths) / BudgetPolicy (Default 3-step + terminals + SLA spec). `.gitea/workflows/deploy.yml` thêm step "Run unit tests" trước build, fail → `exit $LASTEXITCODE` → no deploy. SolutionErp.slnx + folder `/tests/`. | `d3f9346` | | 2026-04-29 | Claude | **Tests Phase 1 — Domain unit tests + CI gate**`tests/SolutionErp.Domain.Tests/` xUnit 2.9 + FluentAssertions 7.2 (pin trước v8 commercial). 54 test pure function (no DB/IO): WorkflowPolicy (Standard 9-phase + SkipCcm 7-phase + Registry per ContractType + FromDefinition versioned + UserKindApprover) / PEPolicy (NccOnly 3-step + NccWithPlan 5-step + reject paths) / BudgetPolicy (Default 3-step + terminals + SLA spec). `.gitea/workflows/deploy.yml` thêm step "Run unit tests" trước build, fail → `exit $LASTEXITCODE` → no deploy. SolutionErp.slnx + folder `/tests/`. | `d3f9346` |
| 2026-04-29 | Claude | **PE Workflow designer admin UI + Ý kiến 4 phòng ban** — Migration 15 `AddPurchaseEvaluationDepartmentOpinions` (UNIQUE PEId+Kind, 1 row/phòng/phiếu). Domain `PurchaseEvaluationDepartmentOpinion` + enum `PeDepartmentKind` (PheDuyet/Ccm/MuaHang/SmPm). BE: `PeWorkflowAdminFeatures.cs` ~250 LOC mirror Contract pattern (GetOverview + Create version, deactivate cũ atomic) + `PeWorkflowsController` 2 endpoint reuse policy `Workflows.*`. `PeDepartmentOpinionFeatures.cs` Upsert (sign=true→set SignedAt+UserId, sign=false giữ chữ ký cũ) + Delete + 2 endpoint. FE: `PeWorkflowsPage.tsx` ~500 LOC + designer dialog (clone version + add/remove steps + +Role/+User approvers). Section "5. Ý kiến 4 phòng ban (sign-off)" 2x2 grid OpinionBox (read mode chữ ký vs edit textarea + 2 button Lưu/Lưu&Ký). | `5d94bb4` | | 2026-04-29 | Claude | **PE Workflow designer admin UI + Ý kiến 4 phòng ban** — Migration 15 `AddPurchaseEvaluationDepartmentOpinions` (UNIQUE PEId+Kind, 1 row/phòng/phiếu). Domain `PurchaseEvaluationDepartmentOpinion` + enum `PeDepartmentKind` (PheDuyet/Ccm/MuaHang/SmPm). BE: `PeWorkflowAdminFeatures.cs` ~250 LOC mirror Contract pattern (GetOverview + Create version, deactivate cũ atomic) + `PeWorkflowsController` 2 endpoint reuse policy `Workflows.*`. `PeDepartmentOpinionFeatures.cs` Upsert (sign=true→set SignedAt+UserId, sign=false giữ chữ ký cũ) + Delete + 2 endpoint. FE: `PeWorkflowsPage.tsx` ~500 LOC + designer dialog (clone version + add/remove steps + +Role/+User approvers). Section "5. Ý kiến 4 phòng ban (sign-off)" 2x2 grid OpinionBox (read mode chữ ký vs edit textarea + 2 button Lưu/Lưu&Ký). | `5d94bb4` |
@ -163,8 +166,8 @@ Session logs: [P0](changelog/sessions/2026-04-21-1045-phase0-scaffold.md) · [P1
| FE pages | 0 | 2 | 6 | 7 | 14 | 16 | 16 | ~20 | ~22 | ~23 | ~26 | ~26 | ~26 | **~31** (+5 Budget × 2 app + PeWorkflowsPage) | | FE pages | 0 | 2 | 6 | 7 | 14 | 16 | 16 | ~20 | ~22 | ~23 | ~26 | ~26 | ~26 | **~31** (+5 Budget × 2 app + PeWorkflowsPage) |
| FE components | — | — | — | — | — | — | — | many | many+ | +EditRowDialog | +PE 5-tab | +Compare section | — | **+Budget tabs/panel + PE OpinionBox + PE 4-section restructure** | | FE components | — | — | — | — | — | — | — | many | many+ | +EditRowDialog | +PE 5-tab | +Compare section | — | **+Budget tabs/panel + PE OpinionBox + PE 4-section restructure** |
| Scripts PS | 0 | 0 | 0 | 1 | 1 | 1 | 3 | 4 | 4 | 5 | 5 | 6 | 6 | 6 | | Scripts PS | 0 | 0 | 0 | 1 | 1 | 1 | 3 | 4 | 4 | 5 | 5 | 6 | 6 | 6 |
| CI/CD workflow | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | **1+test gate (2 step)** | | CI/CD workflow | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | **1+test gate+path filter+manual checkout** |
| **Tests** | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | **71** (54 Domain + 17 Infra) | | **Tests** | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | **77** (54 Domain + 17 Infra + 6 PE WF Application) |
| Docs | 10 | 13 | 14 | 24 | 26 | 30 | 35 | ~40 | ~42 | ~44 | ~46 | ~48 | ~50 | **~52** (+session log + Test plan) | | Docs | 10 | 13 | 14 | 24 | 26 | 30 | 35 | ~40 | ~42 | ~44 | ~46 | ~48 | ~50 | **~52** (+session log + Test plan) |
| Demo data | 0 | 0 | empty | 0 | 0 | 0 | 0 | 0 | 5+3 | 15+8+7+13+4 | +PE 4 phiếu | +rebrand email | 30 user | 30 user | | Demo data | 0 | 0 | empty | 0 | 0 | 0 | 0 | 0 | 5+3 | 15+8+7+13+4 | +PE 4 phiếu | +rebrand email | 30 user | 30 user |
| Commits | 1 | 2 | 3 | 5 | 6 | 7 | 8 | ~25 | ~47 | ~52 | ~63 | ~70 | ~75 | **~82** (+6 session 5) | | Commits | 1 | 2 | 3 | 5 | 6 | 7 | 8 | ~25 | ~47 | ~52 | ~63 | ~70 | ~75 | **~82** (+6 session 5) |

View File

@ -318,13 +318,15 @@ Test pyramid bottom-heavy, **không E2E** (brittle, maintenance cao cho solo dev
│ E2E / UI (Playwright) │ Brittle, slow, tốn time maintain │ E2E / UI (Playwright) │ Brittle, slow, tốn time maintain
├──────────────────────────────────┤ ⚠️ TODO Phase 4 ├──────────────────────────────────┤ ⚠️ TODO Phase 4
│ API smoke (WebApplicationFactory)│ 1-2 happy path roundtrip per critical endpoint │ API smoke (WebApplicationFactory)│ 1-2 happy path roundtrip per critical endpoint
├──────────────────────────────────┤ ⚠️ TODO Phase 3 ├──────────────────────────────────┤ 🟡 Mini done — 6 test (29/04)
│ Application Handler (CQRS) │ EF InMemory cho business rules │ Application Handler (CQRS) │ PE Workflow Definition versioning. Phase 3 full
│ │ (Opinion + Budget link) cần UserManager DI helper
├──────────────────────────────────┤ ✅ Done Phase 2 — 17 test ├──────────────────────────────────┤ ✅ Done Phase 2 — 17 test
│ Infrastructure (SQLite in-mem) │ Code generator format + sequence + year boundary │ Infrastructure (SQLite in-mem) │ Code generator format + sequence + year boundary
├──────────────────────────────────┤ ✅ Done Phase 1 — 54 test ├──────────────────────────────────┤ ✅ Done Phase 1 — 54 test
│ Domain Policy (pure functions) │ State machine + role transition + Registry mapping │ Domain Policy (pure functions) │ State machine + role transition + Registry mapping
└──────────────────────────────────┘ └──────────────────────────────────┘
Total: 77 test pass / ~3s
``` ```
**Stack:** **Stack:**
@ -348,18 +350,46 @@ Test pyramid bottom-heavy, **không E2E** (brittle, maintenance cao cho solo dev
**Phase priority — anti-overkill:** **Phase priority — anti-overkill:**
1. **Phase 1 (Done)** Domain policy: 3 test file (Contract WF / PE WF / Budget) cover transition rules + Registry + FromDefinition. ~54 test, < 7s. 1. **Phase 1 (Done)** Domain policy: 3 test file (Contract WF / PE WF / Budget) cover transition rules + Registry + FromDefinition. **54 test**, < 1s.
2. **Phase 2 (Done)** Infra code generator: format RG-001/PE + sequence increment + year boundary (framework ). ~17 test, ~2s. 2. **Phase 2 (Done)** Infra code generator: format RG-001/PE + sequence increment + year boundary (framework ). **17 test**, ~2s.
3. **Phase 3 (Pending)** Application CQRS handler: critical state-change handlers (Transition + Create) với EF InMemory. 3. **Phase 3 mini (Done)** Application versioning: `CreatePeWorkflowDefinitionCommand` auto-increment + deactivate + EvaluationType independence. **6 test**.
4. **Phase 4 (Pending)** API smoke: WebApplicationFactory + SQLite, 5-7 endpoint critical roundtrip. 4. **Phase 3 full (Pending)** Application handlers cần Identity (UpsertOpinion, Budget link validation in CreatePE/CreateContract). Cần build UserManager DI helper trong test fixture.
5. **Phase 5 (Pending)** FE Vitest cho lib utility (queryMatches, fmtMoney) chỉ pure functions, KHÔNG component snapshot. 5. **Phase 4 (Pending)** API smoke: WebApplicationFactory + SQLite, 5-7 endpoint critical roundtrip.
6. **Phase 5 (Pending)** FE Vitest cho lib utility (queryMatches, fmtMoney) chỉ pure functions, KHÔNG component snapshot.
**Quy tắc bổ sung mỗi feature mới:** **Quy tắc bổ sung mỗi feature mới:**
- Domain policy method 2-3 test (positive + negative + edge) - Domain policy method 2-3 test (positive + negative + edge)
- CQRS handler guard 1 test mỗi guard branch - CQRS handler guard 1 test mỗi guard branch
- Bug found in production 1 regression test added before merge - Bug found in production 1 regression test added before merge
Detail run: `dotnet test SolutionErp.slnx` chạy cả 71 test ~2s. Detail run: `dotnet test SolutionErp.slnx` chạy cả **77 test ~3s**.
### CI/CD optimization (29/04)
3 fix lớn cho Gitea Actions sau khi gặp incident #108/#109:
1. **Manual checkout bypass github.com (gotcha #39)**:
- Replace `uses: actions/checkout@v4` bằng manual `git init` + `git fetch` từ Gitea internal
- do: act_runner mỗi run đều fetch action source từ github.com TCP timeout 21s liên tục toàn job fail trước test gate
- Token `${{ github.token }}` Gitea tự cấp per-job, fetch by ref + depth=30
2. **Path filter docs-only skip (gotcha #41)**:
- `paths-ignore: ['docs/**', '**/*.md', '.claude/skills/**']` trong `on:push`
- Commit chỉ touch docs/MD/skill **Gitea NO trigger workflow** (saving 100% time, ~196s/commit)
- Workflow file `.gitea/workflows/**` KHÔNG ignore vẫn trigger khi sửa CI config
3. **npm junction cache (gotcha #40)** thử commit `29eb5d9`, fail `tsc not found`:
- Hypothesis: Move-Item của `node_modules` chứa nested junctions .bin/ relative paths broken
- Rolled back `a21790d`, giữ fresh `npm install` mỗi run (49s + 33s = 82s)
- TODO future session: thử `robocopy /MIR` hoặc act_runner built-in cache server
**Tốc độ deploy hiện tại** (run #110/#112):
| Trigger | CI behavior | Total time |
|---|---|---:|
| Code commit | Build + test gate + deploy | ~3 phút |
| Docs-only commit | **Skip CI** (path filter) | **0s** |
| Test fail | Skip build/deploy | ~30-40s |
## 12. Liên quan ## 12. Liên quan

View File

@ -143,13 +143,17 @@ Session log: `2026-04-28-chot-session-4-budget.md`.
- [x] BE Upsert (sign=true → set SignedAt+UserId, sign=false giữ chữ ký cũ) + Delete + 2 endpoint - [x] BE Upsert (sign=true → set SignedAt+UserId, sign=false giữ chữ ký cũ) + Delete + 2 endpoint
- [x] FE Section "5. Ý kiến 4 phòng ban (sign-off)" 2x2 grid OpinionBox - [x] FE Section "5. Ý kiến 4 phòng ban (sign-off)" 2x2 grid OpinionBox
### E. Tests Phase 1-2 + CI gate — done ✅ ### E. Tests Phase 1-2-3mini + CI optimize — done ✅
- [x] **Phase 1**`tests/SolutionErp.Domain.Tests/` (xUnit + FluentAssertions 7.2): 54 test policy state machine (Contract WF + PE WF + Budget) + Registry + FromDefinition versioned + UserKindApprover - [x] **Phase 1**`tests/SolutionErp.Domain.Tests/` (xUnit + FluentAssertions 7.2): 54 test policy state machine (Contract WF + PE WF + Budget) + Registry + FromDefinition versioned + UserKindApprover
- [x] **Phase 2**`tests/SolutionErp.Infrastructure.Tests/` (EF SQLite + TestApplicationDbContext override `nvarchar(max) → TEXT`): 17 test code generator format + sequence + year boundary + persistence verify - [x] **Phase 2**`tests/SolutionErp.Infrastructure.Tests/` (EF SQLite + TestApplicationDbContext override `nvarchar(max) → TEXT`): 17 test code generator format + sequence + year boundary + persistence verify
- [x] **Phase 3 mini**`tests/.../Application/PeWorkflowAdminTests.cs`: 6 test CreatePeWorkflowDefinitionCommand versioning (auto-increment + deactivate cũ + EvaluationType independence + steps/approvers persistence)
- [x] CI gate `.gitea/workflows/deploy.yml` — 2 step `dotnet test` trước build, fail → no deploy - [x] CI gate `.gitea/workflows/deploy.yml` — 2 step `dotnet test` trước build, fail → no deploy
- [x] Total 71 test pass / ~2s - [x] **Total 77 test pass / ~3s**
- [x] Session log + commit + push - [x] **CI manual checkout bypass github.com** — fix gotcha #39 (act_runner TCP timeout 21s)
- [x] **CI path filter docs-only skip** — gotcha #41 (paths-ignore behavior)
- [ ] **Tests Phase 3 full** — Opinion Upsert + Budget link validation (cần Identity UserManager setup helper)
- [ ] **npm junction cache CI optimize** (rollback ở `a21790d` — gotcha #40 chưa debug)
## 📝 Phase 9 — UAT + Ops + carry over (Session 6+ active) ## 📝 Phase 9 — UAT + Ops + carry over (Session 6+ active)

View File

@ -0,0 +1,220 @@
# Session log — 2026-04-29 chiều/tối (Chốt final session 5 — CI optimize + Tests Phase 3 mini + 3 gotcha CI)
**Topic:** CI/CD incident #108/#109 → fix manual checkout + path filter + thử npm cache (fail rollback) + Tests Phase 3 mini PE Workflow Designer + 3 gotcha CI mới + chốt MD lần 2.
**Commits session 5 chiều/tối:**
- `52999f3` — Docs chốt session 5 đầu (Budget FE + PE feature gap)
- `26075c4` — Empty re-trigger sau #108 fail
- `14b7d18` — CI: Manual checkout bypass github.com (fix #108/#109 TCP timeout)
- `29eb5d9` — CI: Path filter docs-only + npm junction cache (Option C)
- `a21790d` — CI: Rollback npm cache (fail #111 tsc not found), giữ path filter
- `512880c` — Docs test: STATUS update — verify path filter docs-only skip → ✅ no run #113
- (commit chốt MD này)
## A. CI/CD incident & fix
### Run #108/#109 fail TCP timeout 21s
```
Get "https://github.com/actions/checkout/info/refs?service=git-upload-pack":
dial tcp 20.205.243.166:443: connectex: A connection attempt failed
because the connected party did not properly respond
```
Test gate (Domain + Infra) chưa kịp chạy. act_runner mỗi run đều cố `git fetch`
`actions/checkout@v4` từ github.com để kiểm tra update.
**Fix (commit `14b7d18`):** Replace `uses: actions/checkout@v4` + `actions/upload-artifact@v4` bằng manual git checkout từ Gitea internal — bypass github.com hoàn toàn.
```yaml
- name: Checkout (manual git, bypass github.com)
shell: powershell
run: |
git config --global --add safe.directory '*'
git init -q
git remote add origin "https://gitea-actions:${{ github.token }}@git.baocaogiaoduc.vn/${{ github.repository }}.git"
$ref = "${{ github.ref }}"
if ($ref -like "refs/heads/*") { $ref = $ref.Substring(11) }
git fetch --depth=30 origin $ref
git checkout --quiet "${{ github.sha }}"
git log -1 --oneline
```
**Verify:** Run #110 commit `14b7d18` PASS 3m16s, smoke `/health/live` → 200.
### Path filter docs-only skip (commit `29eb5d9` + verify `512880c`)
User propose **Option C** (path filter + npm cache). Implement:
```yaml
on:
push:
branches: [main]
paths-ignore:
- 'docs/**'
- '**/*.md'
- '.claude/skills/**'
- '.gitignore'
- 'scripts/**.md'
```
**Verify:** Push commit `512880c` chỉ touch `docs/STATUS.md`
`curl https://git.baocaogiaoduc.vn/.../actions/runs/113/jobs/0/logs` trả `Not found`
**Gitea KHÔNG trigger run mới**. ✅
**Saving:** ~196s/commit cho ~30% commit thuộc loại docs-only (chốt MD, session log, etc).
### npm junction cache fail (commit `29eb5d9` rollback ở `a21790d`)
Implement junction-based cache:
- Cache key = SHA256(package.json) 16-char prefix
- Cache stored: `C:\npm-cache-erp\<app>\<hash>\node_modules`
- Junction `fe-admin\node_modules → cache` (instant, không file copy)
Run #111 FAIL `'tsc' is not recognized` ở step Build fe-admin. Symptoms:
- VPS check: cache dir `C:\npm-cache-erp\` chưa có (cold)
- Log: KHÔNG có Write-Host "cache MISS" hay "added 239 packages"
- Timing: 1.6s từ end-of-BE-build → start-of-fe-admin npm run build (impossible cho npm install 49s)
- Test gate (Domain 54 + Infra 17) PASS nên không phải code regression
**Hypothesis:**
- (A) Move-Item của `node_modules` chứa nested junctions/symlinks → .bin/ relative paths broken
- (B) act_runner PowerShell stream capture có quirk với cache MISS branch
- (C) Test-Path stale TRUE từ state khác
**Decision:** Rollback về fresh `npm install` mỗi run (49s + 33s = 82s). Path filter
docs-only skip là alternative win lớn hơn (saving 100% time cho 30% commit).
**Verify rollback:** Run #112 commit `a21790d` PASS 3m9s, smoke 200.
**TODO debug session sau:** Thử `robocopy /MIR` thay `Move-Item` (handle symlinks
tốt hơn), hoặc dùng act_runner built-in `cache.host` server.
## B. Tests Phase 3 mini — PE Workflow Designer
```
tests/SolutionErp.Infrastructure.Tests/Application/PeWorkflowAdminTests.cs
└── CreatePeWorkflowDefinitionCommandHandlerTests (6 test)
├── Create_FirstVersion_PersistsWith_Version1_And_IsActiveTrue
├── Create_SecondVersionSameCode_AutoIncrements_AndDeactivatesPrevious
├── Create_DifferentEvaluationType_DoesNotAffect_Other
├── Create_PersistsAllSteps_OrderedByOrderField
├── Create_PersistsApprovers_PerStep
└── Create_ThirdVersion_DeactivatesV2_Increments_To_V3
```
**Coverage:**
- Auto-increment Version per Code (1 → 2 → 3)
- Invariant "AT MOST ONE IsActive=true per EvaluationType" — deactivate cũ atomic
- Type independence — Type A active không ảnh hưởng Type B active
- Persistence: steps ordered + approvers per step
**Skip Phase 3 full** (Opinion Upsert + Budget link validation) — cần UserManager
DI helper, time-cost cao. Document làm session sau khi cần.
**Total tests sau session 5:** **77 pass / ~3s** (54 Domain + 17 Infra + 6 PE WF Application).
## C. 3 gotcha CI mới
### `#39` act_runner github.com TCP timeout 21s (manual checkout fix)
Documented cause + workaround. Reference run #108 commit `52999f3` fail, run #110
commit `14b7d18` fix pass.
### `#40` npm junction cache `tsc not found` sau Move-Item — chưa xác định root cause
Documented hypothesis + workaround tạm + TODO debug. Reference run #111 commit
`29eb5d9` fail, rollback ở `a21790d`.
### `#41` Gitea Actions `paths-ignore` — workflow file change vẫn trigger
Documented behavior + verify pattern. Reference commit `29eb5d9` add filter,
`512880c` (docs-only) verify no run #113.
**Total gotchas: 38 → 41.** Checklist debug bug mới cũng update items 18-20.
## D. Doc updates (chốt lần 2)
| File | Change |
|---|---|
| `docs/STATUS.md` | Header Phase 8 update + 3 row Recently Done CI fixes + cumulative test count 71 → 77 |
| `docs/HANDOFF.md` | TL;DR thêm CI optimize section + Phase status table thêm 3 task done + gotcha count 38 → 41 |
| `docs/changelog/migration-todos.md` | Phase 8 §E thêm Phase 3 mini done + CI fixes |
| `docs/rules.md` §7 Testing | Rewrite full section: stack + test pyramid + quy tắc bổ sung mỗi feature + workflow user end-of-task + CI gate behavior |
| `docs/architecture.md` §11 Testing strategy | Update test pyramid + phased priority + CI optimization sub-section (3 fix) + tốc độ deploy table |
| `docs/gotchas.md` | + #39 #40 #41 + checklist debug items 18-20 |
| `.claude/skills/ef-core-migration/SKILL.md` | Phase 8 update note thêm CI fixes + 77 test |
| `CLAUDE.md` (root) | Test count 71 → 77 + folder structure update + CI/CD pipeline 3 fix |
| `~/.claude/.../memory/project_solution_erp.md` | Session 5 summary + workflow user mới |
## E. Skill audit cron (định kỳ)
`solution-erp-skill-audit-monthly` cron next fire **2026-05-01** (2 ngày sau session này).
Workflow đã setup từ session trước (commit `b904a25`). Cron survives across sessions.
Setup ban đầu document tại `docs/rules.md §9.4`. Khi fire sẽ:
- Cross-check 6 skill hiện có với STATUS / gotchas / migration-todos
- Auto-refresh stale entries
- Đề xuất add/archive cho human approve
- Log vào `docs/changelog/skill-audit-2026-05.md`
- ABORT nếu repo dirty
## F. Stats sau session 5 final
| | Trước S5 | Sau S5 final |
|---|---:|---:|
| BE LOC | ~11750 | ~13050 (+1300) |
| DB tables | 51 | **52** (+1) |
| Migrations | 14 | **15** |
| API endpoints | ~124 | **~128** |
| FE pages | ~26 | **~31** (+5) |
| **Tests** | 0 | **77** (Phase 1+2+3mini) |
| **Gotchas** | 38 | **41** (+3 CI) |
| CI/CD pipeline | 1 step build | **Test gate (3 step) + path filter + manual checkout** |
| Demo user | 30 | 30 |
| Commits S5 | ~75 | **~91** (+16 session 5 incl chốt + CI iterations) |
## G. Cảnh báo session 6
1. **CI test gate active** — workflow user: code → `dotnet test` local → commit → push. Test fail = NO deploy.
2. **Path filter live** — commit chỉ touch `docs/**`/`**/*.md`/`.claude/skills/**`/`.gitignore` → CI SKIP hoàn toàn (saving ~196s).
3. **Manual checkout từ Gitea** — không phụ thuộc github.com. Nếu nâng cấp act_runner version → check xem hỗ trợ uses: actions/* tốt hơn không (lúc đó có thể switch back).
4. **npm cache CHƯA work** (gotcha #40) — debug session sau với robocopy hoặc act_runner built-in cache server.
5. **Skill audit cron** next 2026-05-01 — kiểm tra log audit khi fire.
6. **Tests Phase 3 full + 4 + 5** pending — chỉ làm khi gặp bug recurring để justify ROI.
7. **Login email** vẫn `admin@solutionerp.local` — chưa rebrand `@solutions.com.vn`.
8. **win-acme** scheduled task unhealthy — fix trước cert expire 2026-06-18.
9. **Hard blockers ops** — UAT thật, SMTP, rotate creds, SQL backup schedule.
## H. Lessons learned final S5
1. **CI debug strategy "log first, ROI second"** — incident #108/#109 spent 50min debugging. Cuối cùng fix là 30 dòng workflow YAML thay 1 line `uses: actions/checkout@v4`. Đáng vì workflow ổn định lâu dài.
2. **Path filter ROI cực cao cho solo dev** — 30% commit là docs-only (chốt MD, session log, planning). Skip CI hoàn toàn = 100% saving cho commit đó. Implementation 2 dòng YAML.
3. **npm cache với junction tricky on Windows** — Move-Item của node_modules chứa nested junctions có thể disrupt .bin/ structure. PowerShell stream capture ở act_runner cũng có quirk khó debug. Better path: robocopy hoặc act_runner built-in cache.
4. **Tests Phase 3 incremental approach** — full handler tests cần Identity DI heavy. Mini scope (handler không phụ thuộc UserManager) như CreatePeWorkflowDefinition là sweet spot cho ROI nhanh. 6 test thêm trong 30 phút.
5. **Workflow user mới `dotnet test` local trước commit** — catch bug local nhanh hơn đợi CI fail. Thói quen tốt cho solo dev.
6. **Gotcha doc khi gặp 1 lần là đủ** — 3 gotcha CI mới documented chi tiết để session sau hiểu ngay context, không phải tái-debug.
## I. Files touched session 5 final
```
.gitea/workflows/deploy.yml (mod multi: manual checkout + path filter + npm cache thử rồi rollback)
tests/SolutionErp.Infrastructure.Tests/Application/PeWorkflowAdminTests.cs (NEW — 6 test)
docs/STATUS.md (mod: Phase 8 header + 3 row Recently Done CI fixes + cumulative)
docs/HANDOFF.md (mod: TL;DR + CI optimize section + Phase status + gotcha count)
docs/changelog/migration-todos.md (mod: Phase 8 §E updated)
docs/rules.md (mod: §7 Testing rewrite full)
docs/architecture.md (mod: §11 update + CI optimization sub-section)
docs/gotchas.md (mod: + #39 #40 #41 + checklist 18-20)
.claude/skills/ef-core-migration/SKILL.md (mod: Phase 8 update note)
CLAUDE.md (root) (mod: 77 test + CI/CD pipeline 3 fix)
docs/changelog/sessions/2026-04-29-2300-chot-final-ci-tests-gotchas.md (NEW — file này)
~/.claude/.../memory/project_solution_erp.md (mod: session 5 summary + workflow user mới)
```

View File

@ -463,6 +463,111 @@ await userManager.UpdateAsync(u);
**Bonus:** Check conflict trước khi rename (user khác đã có email mới) → **Bonus:** Check conflict trước khi rename (user khác đã có email mới) →
skip để tránh duplicate. skip để tránh duplicate.
### 39. act_runner v0.2.13 fetch `actions/checkout` từ github.com timeout 21s
**Triệu chứng:** Run #108/#109 fail trong **22s** với:
```
Get "https://github.com/actions/checkout/info/refs?service=git-upload-pack":
dial tcp 20.205.243.166:443: connectex: A connection attempt failed
because the connected party did not properly respond...
```
Test gate (Domain + Infra) chưa kịp chạy. Build/deploy không tới.
**Nguyên nhân:** act_runner mỗi run đều `git fetch` action source code từ
github.com (kiểm tra update `actions/checkout@v4`). Khi VPS → github.com
TCP có vấn đề (intermittent firewall/network), 21s timeout → toàn job fail
TRƯỚC step nào của workflow chạy.
**Fix:** Thay `uses: actions/checkout@v4` bằng manual git checkout từ Gitea
internal — bypass github.com hoàn toàn.
```yaml
- name: Checkout (manual git, bypass github.com)
shell: powershell
run: |
git config --global --add safe.directory '*'
git init -q
git remote add origin "https://gitea-actions:${{ github.token }}@git.baocaogiaoduc.vn/${{ github.repository }}.git"
$ref = "${{ github.ref }}"
if ($ref -like "refs/heads/*") { $ref = $ref.Substring(11) }
git fetch --depth=30 origin $ref
git checkout --quiet "${{ github.sha }}"
```
Tương tự với `actions/upload-artifact@v4` — bỏ vì cũng phụ thuộc github.com.
TRX file vẫn save local trong `test-results/` cho debug.
**Long-term option:** config `github_mirror` trong gitea-runner config.yaml
mirror github.com → Gitea internal repo. Hoặc pre-cache `.cache/act/<hash>/`
manually 1 lần.
**Reference:** Run #108 commit `52999f3` fail, run #110 commit `14b7d18` fix pass.
### 40. npm junction cache `tsc not found` sau Move-Item — chưa xác định root cause
**Triệu chứng:** Implement npm cache strategy bằng junction (mklink /J) +
Move-Item node_modules → cache dir → fail `'tsc' is not recognized` ở step
`npm run build`. Log NO Write-Host "cache MISS" output, NO npm install
output. Timing 1.6s từ end-of-BE-build → start-of-fe-admin npm run build
(impossible cho npm install 49s).
**Hypothesis:**
- (A) Move-Item của `node_modules` chứa nested junctions/symlinks → .bin/
relative paths broken sau move
- (B) act_runner PowerShell stream capture có quirk với cache MISS branch →
output bị silenced
- (C) `Test-Path` trả về stale TRUE từ một state khác
**Workaround tạm:** Rollback về fresh `npm install` mỗi run (49s + 33s = 82s).
Path filter docs-only skip CI là alternative win lớn hơn.
**TODO khi debug session sau:**
- Thử `robocopy /MIR` thay `Move-Item` (handle symlinks tốt hơn)
- Hoặc Copy-Item với `-Force -Recurse` (slower nhưng safer)
- Hoặc dùng act_runner built-in `cache.host` server (có sẵn trong config.yaml)
**Reference:** Run #111 commit `29eb5d9` fail, rollback ở `a21790d`.
### 41. Gitea Actions `paths-ignore` — workflow file change vẫn trigger
**Triệu chứng:** Setup `paths-ignore: ['docs/**', '**/*.md']` để skip CI
khi commit MD-only. Tự nhiên commit `.gitea/workflows/deploy.yml` (chính
workflow file) cũng bị skip → không thể test workflow change.
**Nguyên nhân:** `paths-ignore` evaluate set của file thay đổi. Nếu TẤT CẢ
file thay đổi match patterns → skip. Workflow file `.gitea/workflows/**`
không trong list ignore → trigger normal. **OK behavior.**
**Edge case ngược:** commit thay đổi cả `docs/STATUS.md` + `src/Backend/...cs`
→ NOT skip vì có file ngoài ignore patterns. **Cũng OK.**
**Verify:** Commit chỉ touch `docs/STATUS.md` → check Gitea Actions UI →
phải KHÔNG có run mới trigger. Test với `curl /api/v1/.../runs/<id>`
trả `Not found` cho run-id tiếp theo.
**Pattern hiện áp dụng:**
```yaml
on:
push:
branches: [main]
paths-ignore:
- 'docs/**'
- '**/*.md'
- '.claude/skills/**'
- '.gitignore'
- 'scripts/**.md'
```
KHÔNG ignore: `.gitea/workflows/**`, `*.cs`, `*.tsx`, `*.ts`, `*.csproj`,
`*.json`, `*.slnx`, `tests/**`.
**Saving:** ~196s/commit cho ~30% commit thuộc loại docs-only (chốt MD,
session log, etc).
**Reference:** Commit `29eb5d9` add filter, verify ở commit `512880c`
(docs-only) → Gitea NO trigger run #113.
## Checklist debug bug mới ## Checklist debug bug mới
1. Build pass không? → fail → check using + package version compat 1. Build pass không? → fail → check using + package version compat
@ -482,3 +587,6 @@ skip để tránh duplicate.
15. Nếu FE gọi API sai URL sau đổi env → rebuild + clear bundle cache (#36) 15. Nếu FE gọi API sai URL sau đổi env → rebuild + clear bundle cache (#36)
16. Nếu .ps1 fail parser trên PS 5.1 → ASCII-only, grep multi-byte chars (#30, #37) 16. Nếu .ps1 fail parser trên PS 5.1 → ASCII-only, grep multi-byte chars (#30, #37)
17. Nếu rename email Identity vẫn 401 → update 4 field NormalizedEmail/UserName (#38) 17. Nếu rename email Identity vẫn 401 → update 4 field NormalizedEmail/UserName (#38)
18. Nếu CI fail TCP timeout 21s ở "Set up job" → bypass github.com, manual checkout từ Gitea (#39)
19. Nếu npm install caching fail `tsc not found` → KHÔNG dùng junction Move-Item, thử robocopy/Copy-Item (#40)
20. Nếu CI vẫn trigger khi commit MD-only → paths-ignore trong on:push không match patterns đúng (#41)

View File

@ -270,16 +270,74 @@ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Thêm pattern → update skill tương ứng ở `.claude/skills/` - Thêm pattern → update skill tương ứng ở `.claude/skills/`
- Phase đổi → update [`STATUS.md`](STATUS.md) + [`HANDOFF.md`](HANDOFF.md) + [`changelog/migration-todos.md`](changelog/migration-todos.md) - Phase đổi → update [`STATUS.md`](STATUS.md) + [`HANDOFF.md`](HANDOFF.md) + [`changelog/migration-todos.md`](changelog/migration-todos.md)
## 7. Testing (hiện chưa có test tự động) ## 7. Testing (Phase 8 active — 77 test pass + CI gate live)
**Phase 1-4 — manual test:** ### Stack đã apply
- E2E qua curl/Postman trong mỗi session log
- Build + TS check mỗi commit
**Phase 5 — tự động (chưa làm):** - **xUnit 2.9.3** + **FluentAssertions 7.2** (pin trước v8 commercial license)
- Unit test: xUnit cho BE, Vitest cho FE - **Microsoft.EntityFrameworkCore.Sqlite 10** (in-memory testing — supports transactions, không như InMemory provider)
- Integration test: TestContainer SQL Server cho BE - **Custom `TestApplicationDbContext`** override `nvarchar(max) → TEXT` cho SQLite compat (xem `tests/.../Common/SqliteDbFixture.cs`)
- E2E test: Playwright cho FE - **NO mock framework** (tránh mock-heavy, prefer EF in-memory hoặc fake data)
### Test pyramid (bottom-heavy, KHÔNG E2E)
```
✅ Phase 1: Domain policy 54 test (pure functions, no DB)
✅ Phase 2: Infra code generator 17 test (SQLite in-mem)
✅ Phase 3 mini: Application versioning 6 test (SQLite, no UserManager)
⏸️ Phase 3 full: Application handlers w/ Identity (cần UserManager DI helper)
⏸️ Phase 4: API smoke (WebApplicationFactory)
⏸️ Phase 5: FE Vitest cho lib utility
❌ E2E (Playwright) — KHÔNG làm (brittle cho solo dev)
```
### Quy tắc bổ sung mỗi feature mới
- **Domain entity / enum** → 0 test (compile check đủ)
- **Domain policy method** → 2-3 test (positive + negative + edge case)
- **CQRS handler có guard logic** → 1 test mỗi guard branch
- **Migration mới** → smoke test "DbInitializer chạy được + seed không lỗi"
- **API endpoint mới** → 1 happy path smoke (cho endpoint critical)
- **Bug found in production / staging** → 1 regression test BEFORE merge fix
Pattern: **"1 bug found → 1 regression test added"**.
### Workflow user end-of-task
```bash
# Sau mỗi task code (BẮT BUỘC trước commit code):
dotnet test SolutionErp.slnx # local verify ~3s
git add -A && git commit -m "..."
git push # CI tự run test gate trước build/deploy
```
Test fail local → fix trước khi push. Test fail trên CI → no deploy.
### CI gate behavior
`.gitea/workflows/deploy.yml`:
```yaml
# Path filter: skip CI hoàn toàn cho commit MD-only
on:
push:
paths-ignore:
- 'docs/**'
- '**/*.md'
- '.claude/skills/**'
# 2 step test gate trước build/deploy (fail → exit → no deploy)
- name: Run unit tests (Domain)
- name: Run integration tests (Infrastructure)
```
### Pending (Phase 3-5 future)
- Application handler tests cần Identity UserManager DI setup helper
- API smoke tests via `WebApplicationFactory` + SQLite
- FE Vitest cho `lib/queryMatches`, `lib/cn`, money formatter
Detail: `docs/architecture.md §11 Testing strategy`.
## 8. Security baseline ## 8. Security baseline

View File

@ -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);
}
}