Files
solution-erp/.claude/agent-memory/test-specialist/MEMORY.md
2026-08-08 12:30:07 +07:00

18 KiB

Test-Specialist Agent — Persistent Memory

  • S166-K4a (08-01) 49 row menu KHKK 8-nhóm — 7 test (6 gốc + T7 sau resume), file MỚI Application/KhkkGroupMenuSeedTests.cs, return SẠCH [baseline đo tươi Infra 569 → sau 576, Δ+7, 0 hồi quy · Domain 45 · tổng 621]: BƯỚC ĐẮT NHẤT = TÌM CHỒNG LẤN TRƯỚC KHI VIẾTContractCatalogTests.cs:445 (T6 lane K1) đã assert MenuKeys.All.Except(menuKeysInDb).BeEmpty() sau SeedMenuTreeAsync ⇒ invariant "49 key mới đều có menu-row" TỰ ĐỘNG được phủ, và nó XANH ở baseline = chứng nhân độc lập cho claim by-construction ⇒ KHÔNG nhân bản, chỉ viết cái nó không đo. Kỹ thuật chính (tái dụng S164 ma-trận-cùng-ruột): T5 labelBackfill bôi hỏng nhãn CẢ Khkk_G1 LẪN Khkk_G2 trong 1 lần → seed lại → G1 tự lành / G2 giữ chữ hỏng; khác biệt duy nhất = có/không entry trong labelBackfill ⇒ chứng đúng cơ-chế backfill chứ không phải upsert "tiện tay" ghi Label (gotcha #11). T6 đối-chứng-cùng-lượt: cùng role, Pe_DuyetNcc_List CanCreate=true vs 49 key Khkk CanCreate=false ⇒ chuỗi "false" không phải do seeder no-op. T4 mirror đo DB↔DB (leaf nhóm 2..8 so với row nhóm 1) + chốt trước "6 nhãn nhóm 1 PHÂN BIỆT" — thiếu vế này thì mirror đúng ở mọi thế giới. T1 kỳ vọng = mảng 49 chuỗi VIẾT TAY, KHÔNG gọi lại MenuKeys.KhkkGroup/KhkkLeaf (gọi lại = tautology). 2 bẫy đo đã né: (i) đếm họ Khkk bằng Ordinal in-memory chứ không StartsWith → LIKE SQLite case-insensitive ASCII; (ii) kiểm KHÔNG có HasData cho MenuItems ⇒ 56 row đến TỪ seeder, không phải EF-seed. Số ĐO: MenuKeys.All runtime = 113 (Policies DERIVED 452) — trùng đếm by-source lane BE. 0 bug prod. Tag [s166-k4a, tim-chong-lap-truoc-khi-viet, ma-tran-2-key-1-bien, doi-chung-cung-luot, ordinal-khong-LIKE]

    • +1 (resume-2, nợ gate-K4b MAJOR-A) lọc nhóm màn "Đã xoá" → Infra 577, tổng 622: đặt ở ContractSigningPlanCrudTests.cs CHỨ KHÔNG file K4a — chọn file theo FIXTURE, không theo chủ đề: chỉ fixture đó có AuditingInterceptor (màn "Đã xoá" đọc row IsDeleted=1; fixture thường xoá CỨNG ⇒ màn rỗng ⇒ đo nhầm cơ chế). 2 phiếu CÙNG 1 PE khác nhóm là hợp lệ vì rào khoá theo CẶP (PeId, ApprovalGroup); workflow QT-KHKK-V2 ngoài họ KHKK-N{n} nên rào (v) cho qua. Mọi vế assert Total (dấu vân tay lọc-SERVER: CountAsync sau Where; lọc client-side cho Items y hệt mà Total = cả thế giới) + đo cả nhánh Admin vì vế lọc nằm NGOÀI khối IDOR. 🔴 Mẹo chứng-răng RẺ NHẤT gặp tới giờ: khi vế cần bảo vệ là "handler CÓ đọc param", thế-giới-hỏng tương đương chính xác với việc gọi handler bằng param=null ⇒ RED→GREEN đo được bằng cách đổi ĐÚNG 1 chữ TRONG TEST, chạy trên MÃ PROD THẬT (mạnh hơn probe mimic), 0 rủi ro chạm prod. RED thật: Expected g1.Items to contain 1 item(s), but found 2.
    • +T7 (resume, gate trả finding D MAJOR) Revoke_DoesNotTouch_KhkkGroups → 7/7, Infra 576, tổng 621: bảo vệ ca ĐÃ VỠ THẬT (Run #423 447/494 row rơi, gotcha #84). Bẫy vacuous đọc ra TỪ MÃ chứ không từ prompt: :2546-2551 Admin-role-chưa-seed ⇒ revoker RETURN SỚM ⇒ test không seed Admin sẽ xanh vì revoker CHƯA CHẠY → bắt buộc control dương (2 key Off_*/Personal của CHÍNH 2 role đó phải về false-4-cờ) + vế Admin-giữ-true + vế 112 row Khkk sống. Chứng răng bằng PROBE VI-PHÂN (cấm chạm prod ⇒ không fault-inject được): file tạm dựng lại đúng hình query revoke + thêm ĐÚNG 1 vế StartsWith("Khkk") → ĐO 112→0 ⇒ T7 phân biệt 2 thế giới; probe xoá + verify 2 chiều (git sạch ∧ suite = 569+7 chứ không +8). Đếm bằng đơn vị của sự cố thật (row sống sót) để thông điệp lỗi cùng ngôn ngữ với Run #423.
  • S164-K3 (07-31) KHKK level-finalize + pin-đúng-nhóm — 5 test, return SẠCH [baseline đo tươi 609/0 = 45D+564I KHỚP đề bài · sau: 614/0, Δ+5, 0 hồi quy]: +4 vào ContractSigningPlanApprovalTests.cs (T-a/b/c finalize + T-e giữ-cờ), +1 vào ContractSigningPlanGroupAndCatalogTests.cs (T-d rào (v)) — chọn file thứ 3 thay "file mới" để KHÔNG nhân bản 70 LOC SeedPeForCreateAsync+CreateAsync đã có sẵn ở đó. Kỹ thuật chính = MA TRẬN cùng-ruột-khác-1-biến thay cho fault-inject (không được chạm prod, mà prod đang mang diff lane khác): 1 SeedFinalizeScenarioAsync → T-a (cờ+apply=true ⇒ DaDuyet+chốt giá) / T-b (cùng seed, đổi ĐÚNG applyLevelFinalize:false ⇒ (2,1) advance thường) / T-c (Cấp không cờ + default true ⇒ (0,2)). Bỏ qua cờ ⇒ T-a đỏ · bỏ qua param ⇒ T-b đỏ · || thay && ⇒ T-c đỏ. Điều kiện SỐNG CÒN: đặt cờ ở Bước-2-Cấp-2 của quy trình 3 Bước — cờ ở cấp cuối thì "kết thúc" xảy ra ở CẢ HAI thế giới ⇒ T-a xanh mà chứng minh 0 (assert StepCountAsync==3 để chốt). T-d: CÙNG workflow KHKK-N5 cho 2 kết quả trái ngược (phiếu nhóm 3 = 409 · nhóm 5 = qua) + không-đốt-mã (rào (v) :409-420 đứng TRƯỚC codegen :428) có control-dương 001/002/003 tăng thật. Kiểm dữ-liệu chứ không chỉ đọc regex: DbInitializer.cs:609 sinh đúng $"KHKK-N{n}" ⇒ rào neo thật. T-e: 3 vế (tick⇒true · không-tick⇒false · đối chứng type-1 cũng true ⇒ ECHO chứ không phải đổi ép-false thành ép-true). CỐ Ý KHÔNG pin AllowApproverSkipToFinal==true dù mã đang echo: service KHKK 0 lần đọc cờ đó (:47 khai "không port") ⇒ đó là ứng-viên config-lie #78 CÒN SÓT; pin nó = biến khả-năng-lỗi thành yêu cầu → REPORT lead thay vì test. 0 bug prod. Tag [s164-k3, level-finalize, ma-tran-cung-ruot-1-bien, co-o-cap-cuoi-lam-rong-phep-do, khong-pin-config-lie]

  • S161-W2 (07-29) KHKK CRUD 8 test [hmw wf_7490179a-70b · chết #53 sau khi file LAND (sub-md dừng §0.1) · lead chạy suite tươi 574/0 = 566+8, cả 8 tên đúng]: Application/ContractSigningPlanCrudTests.cs (7) + extend Api/AuthorizePolicyRegressionTests.cs (PIN reflection). 2 PIN spec + 6 acceptance: happy-path Lines=winners + SUM IsSelected đúng NCC (bẫy w.Id vs w.SupplierId) · second-create 409 _AndDoesNotBurnCode (mã không đốt khi Conflict — phép đo sequence side-effect) · workflow-guard 2 nhánh Conflict/NotFound · no-winner · soft-delete-then-recreate + changelog giữ · owner/phase guard. Phát hiện đắt (sub-md §0.1): TestApplicationDbContext KHÔNG wire AuditingInterceptor ⇒ Remove() = xoá CỨNG ⇒ đo "xoá-tạo-lại" trên xoá-cứng = ĐO NHẦM CƠ CHẾ (rào 1-PE-1-KH dựa global-filter che row soft-deleted, không phải row biến mất) → dựng fixture riêng CÓ interceptor cho nhóm delete-test. Baseline đo tươi trước khi viết (không tin số đề bài). Tag [s161-w2, khkk-8-test, khong-dot-ma-khi-409, fixture-interceptor-cho-soft-delete, do-nham-co-che-xoa-cung]

  • S161 (07-29) W1 KHKK: 4 test schema+codegen [hmw wf_a95c9909-14b · chết #53 LÚC ĐỢI full-suite (việc thật đã xong) → sub-md 10KB ghi-từng-mục cứu trọn; lead chạy full-suite tươi 566/0 + điền §2/§4 on-behalf]: File MỚI Common/ContractSigningPlanSchemaTests.cs — 4 test: (1) PIN-spec BayBang_MoiBang_CoAuditFields_VaQueryFilter (quét EF model theo NAMESPACE → BeEquivalentTo đúng bộ 7 tên [bảng #8 lọt = ĐỎ] + audit-fields + soft-delete assert 2-CHIỀU [BaseEntity phải KHÔNG có IsDeleted] + seed IsDeleted=true chứng filter là BIỂU-THỨC-THẬT); (2) PIN SigningPlanCodeGenerator_Format_KhkkYearSeqD3 — regex + tuyệt-đối 001→002 + row LastSeq==2 + đẩy đồng hồ 2027 → KHKK/2027/001 reset (thiếu vế này \d{4} xanh cả khi năm hardcode); (3) #57 xoá-mềm-thêm-lại KHÔNG nổ UNIQUE + vế đối chứng 2-dòng-sống PHẢI DbUpdateException (không có = xanh cả khi index chưa từng tạo); (4) pin slot 10 enum. Fault-inject 2 chiều trên bảng-kỳ-vọng rồi revert — chứng GetDeclaredQueryFilters() phân biệt 2 thế giới. Learned: (1) prompt lead ≠ đĩa — LevelOpinion là AuditableEntity nhưng CỐ Ý KHÔNG filter (lý-do ghi 2 chỗ: site orphan-purge phải thấy row soft-deleted) ⇒ test theo CODE + assert cái-cố-ý thành invariant có răng; (2) EF Core 10: GetQueryFilter() obsolete-prone → (IReadOnlyEntityType).GetDeclaredQueryFilters(); (3) Serializable trên SQLite = graceful-map, tiền lệ SqliteDbFixture.cs:36-42 + PE codegen test — KHÔNG chế fixture mới; (4) KHÔNG Remove() đo xoá mềm (TestCtx không wire AuditingInterceptor → xoá CỨNG, bài S155). Tag [s161, w1-4-test, faultinject-2-chieu, test-theo-code-khong-theo-prompt, clock-2027-reset, 53-luc-doi-suite]

  • S155 (07-27) PE xóa-khi-đang-duyệt +10 → 561 (1 FAIL GIỮ ĐỎ = BUG PROD cascade-detach-Added-child): 2 bài SỐNG CÒN còn dùng: (i) TestApplicationDbContext KHÔNG wire AuditingInterceptorRemove() = XÓA CỨNG — đo xóa-mềm phải dựng fixture riêng có interceptor, không thì assert đúng ở CẢ HAI thế giới; (ii) "Add con → Remove cha → 1 SaveChanges" = con Added bị cascade thành Detached, mất row. Verbose → archive/baseline-history.md §S155.

  • S153 (07-26) fault-inject máy mind-check 18/18 (KHÔNG phải test C#, baseline không đổi): 3 bài còn dùng: (i) fixture-HỎNG cho-xanh trông Y HỆT máy-ĐÚNG cho-xanh ⇒ soi RUỘT fixture (cat -A/repr), đừng chỉ đọc exit code; (ii) cặp CÙNG-RUỘT khác 1 biến = chứng đọc-config-LIVE; (iii) đo wall-clock phải đi nhánh ĐẮT (early-exit = tự đo nhánh rẻ rồi gọi là chi phí thật). Verbose → archive/baseline-history.md §S153.

  • S134 (07-17) PE lũy-kế TẠM TÍNH +8 → 528: khuôn regression "bất động" mạnh nhất = C6 exact-numbers 2 hàm complementary cùng seed (gọi CẢ ComputeAsync + ComputePendingAsync, assert số CHÍNH XÁC 2 phía ⇒ bắt double-count). +bẫy FluentAssertions ContainInOrder(params T[]) overload-clash khi T=Guid → bọc new[]{...}. Verbose → archive/baseline-history.md §S134.

  • S133 (07-17) PE budget-freeze T0 REPRODUCE + T1-T9 → 519: khuôn test-before bug-fix còn dùng: đưa phiếu tới trạng-thái đích QUA service THẬT (TransitionAsync), KHÔNG seed thẳng Phase (post-fix snapshot chỉ set qua transition ⇒ seed thẳng = đo nhầm); 2 sanity-assert chống vacuous TRƯỚC assert chính. ⚠️ 2 invocation cùng role + cùng task-index trong 1 run-folder = sub-file GHI ĐÈ nhau. Verbose → archive/baseline-history.md §S133.

Persistent diary cross-session. Auto-injected first ~200 lines at spawn (L1 HOT). Update BEFORE every stop. Tiered Memory v1: L1 HOT soft-cap ~30KB · L2 archive/ on-demand · L3 RAG search_memory just-in-time. Keep entry ≤ 1.5K chars (gotcha #53). NEW agent S39 (2026-05-29) — dedicated test layer (tách khỏi implementer Case 3).


Role/split-boundary/timing-rules/Pattern-10-11-12/anti-patterns: đã có nguyên văn trong agent-definition (auto-inject mỗi spawn) ⇒ KHÔNG chép lại ở đây. Chỉ giữ thứ agent-def KHÔNG có.

📊 Baseline ĐO TƯƠI 2026-08-01 (S166): 622 = 45 Domain + 577 Infra, 0 FAIL

  • ⚠️ Con số trong agent-definition ("130") là STALE — luôn đo lại dotnet test trước khi báo delta, đừng tin số đề bài.
  • Count history + verbose per-session note (S54→S155): archive/baseline-history.md (L2, on-demand).

Pattern S67: private-static seed/init → invoke qua REFLECTION (GetMethod(name, NonPublic|Static) + Invoke(null, [db, roleManager, NullLogger.Instance])); seed MenuItem rows TRƯỚC Permission (FK MenuKey→MenuItem.Key Cascade, SQLite Error 19 nếu thiếu). Cycle-guard test: SqliteDbFixture đủ (no User); rollup-count test cần IdentityFixture. Run: dotnet test SolutionErp.slnx --nologo --verbosity minimal -p:BuildInParallel=false -maxcpucount:1 (MSBuild OOM → serialize build)

⚠️ Pattern: deduction hook FK → seed LeaveType cho terminal test (S43)

LeaveBalance→LeaveType Restrict FK. ApproveLeaveRequest terminal (DaDuyet) insert LeaveBalance → test tới DaDuyet PHẢI seed 1 LeaveType + LeaveRequest.LeaveTypeId=type.Id (random Guid→FK fail SQLite 19). Non-terminal (advance/reject/return/OtRequest) KHÔNG cần. BuildLeave optional leaveTypeId default random. Year=StartDate.Year. Negative OK (no quota guard). Query lazy-synth Entitled=DaysPerYear khi 0 row.

🎯 Coverage gap backlog (priority — Reviewer flagged S36)

  1. DONE S45 — HrmConfig Holiday composite UNIQUE (Year,Date): 7 test (HrmConfigHolidayTests.cs) + surfaced Mig 43 filtered-index fix
  2. DONE S45 — EmployeeSatellite FK invariant + soft-delete + cascade: 10 test (EmployeeSatelliteTests.cs)
  3. DONE S45 — gotcha #44 authz regression EmployeesController + HrmConfigsController: 10 test (extend AuthorizePolicyRegressionTests.cs)
  4. Phase 10.3 Proposal ApproveV2 (S37) + Workflow Apps skeleton (S38) — test-after khi UAT confirm
  5. gotcha #57 (S51+S52 REPRODUCED): HRM HrmConfigFilteredUniqueTests 2 RED (LeaveType+Shift+OtPolicy) → em main Mig 45 .HasFilter GREEN. S52 EXT Master MasterCatalogFilteredUniqueTests 3 RED (Department cfg:18 / Project:19 / Supplier:24 bare .IsUnique()) → pending em main fix migration. Vehicle+Driver (Mig 44) ĐÃ filtered. Pattern: seed IsDeleted=true slot + Create cùng Code → assert active==1 + IgnoreQueryFilters all==2.

📅 Recent activity (one-line FIFO — verbose in archive/baseline-history.md)

Danh sách FIFO S54→S118b (22 mục) ĐÃ nằm trọn trong archive/baseline-history.md + archive/_INDEX.md — gỡ khỏi index @S166 vì trùng lặp. Dưới đây CHỈ giữ mẹo còn tái dụng:

  • Bẫy đo lặp nhiều lần: (i) MethodInfo.Invoke KHÔNG auto-bind optional param → phải pass đủ arg (S117); (ii) SQLite collation BINARY không bắt dup khác-hoa-thường, prod SQL Server thì CÓ → dedup phải nằm ở SERVICE, đừng dựa DB (S113/D3); (iii) test invoke method CÔ-LẬP qua reflection VẪN PASS dù prod đã tắt call trong chain SeedAsync — reflection-test không chứng chain còn sống (S92).
  • Khuôn assert mạnh: CONTRAST/by-contrast trong CÙNG một lần chạy (S54/S118) · GUARD-FIRST (S60) · RED-proven-via-git stash cho bug-fix test (S86) · boundary strict-< (S69b) · ranh giới net==0 mà từng-dòng≠0 (S116).

🔄 Curate trigger

Index > ~17KB → archive sang L2 (archive/baseline-history.md + con-trỏ trong archive/_INDEX.md). Commit scope (em main commits): Tests.

S161-W7d2 (2026-07-30) — 3 PIN ban-cung Hdc_ (lead VERIFY-APPEND tu memoryDelta return; suite 587→590/0 CI-doc-lap xac nhan)

  • Total cua PagedResult = dau van tay loc SERVER-SIDE: CountAsync sau moi Where ⇒ ban "lay pageSize:100 roi loc client" cho Items Y HET nhung Total = ca the gioi. Items-only KHONG phan biet duoc; +1 dong assert Total thi phan biet.

  • Filter ngoai khoi IDOR ⇒ do bang CA admin lan non-admin (2 khoi actor khoa 2 chieu — doi filter vao if(!admin) thi ban chi-do-1-vai van xanh).

  • Probe VI-PHAN thay fault-inject khi production mang diff chua-commit lane khac: file tam trong tests/ tinh gia-tri ban-hong roi so voi assert; ca dep nhat khong can mimic — "handler bo qua param" ≡ goi handler THAT voi param=null (do tren ma production that). Zero-risk. Xoa xong verify 2 chieu (git sach + so test tut ve dung).

  • Chon hang-xom phase seed theo NGHIA-TU: DangDongDau=8 ten dung la khau ban-cung = moi ngon nhat cho cai-dat sai; TraLai=98/TuChoi=99 >9 ⇒ moi >= sai deu lo.

  • 🔴 Surprise tu-thu: ban dau viet tron §5-§8 kem so chua-do (587→590 + 3 dong fault-inject BIA) — "ghi-tung-muc" bi hieu thanh "ghi dan bai roi dien". Cat ve 98 dong, de vet. So cuoi TRUNG du-doan ⇒ neu khong cat thi khong ai phat hien. Bai: dan-bai co san con-so ky-vong = bay claim-truoc-do tinh-vi nhat (trong y het "ghi dia chong #53"); muc chua chay CHI duoc tieu-de + "CHUA DO".

  • S182 YC-013 (lead-seed, C2 /pause — lane-1 đứt sớm, lane-2 viết đủ rồi chết session-limit SAU khi suite xanh): 14 ca T1-T14 (600→614) + T15 Create_PinQuyTrinhDaNgungHoacChuaGhim_ThiBiChan_VaKhongDotMa (MAJOR-3, 615) ⇒ suite 660/0. Reviewer chấm KHÔNG ca nào nới để xanh; T3 2-chân giết cả bản-vá-regex lẫn bản-vá-đừng-tắt-gì; T5 seed pin-lệch thẳng DbContext + loại trước 2 cửa 409 nhiễu; T11 tiền-đề 2 chiều (filter-on=0/off=1) đo đúng bẫy IgnoreQueryFilters — prod THẬT có ca (KHKK-N1 6 pin/5 xoá-mềm); T12 dangling THẬT trong DB (office loose-0FK). 1 assertion GroupAndCatalog đổi theo câu 409 mới W1b = spec-change có khai; lead siết pin-nguyên-câu-thứ-tự 1 ca (MINOR-1). → runs/2026-08-08-S182-khkk-awf-mirror-ncc/sub-test-specialist-2.md