Appearance
Kế hoạch kiểm thử — Insight Ghi Âm cho BOD
Phiên bản: 1.0 Ngày: 15/05/2026 Mục đích: Test oracle — kỳ vọng hành vi cần verify. KHÔNG copy nguyên FR; chỉ map AC cần test cụ thể.
D1) Coverage
D1.1) Test pyramid
| Loại test | Phạm vi | Tỷ lệ |
|---|---|---|
| Unit test | Chart data transform, date bucketing, WoW delta calc | 40% |
| Component test | Filter dropdown behavior, KPI card render states, modal pagination | 30% |
| Integration test | Permission check + query flow, URL params parsing | 20% |
| E2E test (Cypress) | BOD login → access → drill-down; Staff blocked | 10% |
D1.2) Browser & device
- Browser support: Chrome (latest 3), Safari (latest 2), Firefox (latest 2), Edge (latest 2)
- Resolution: 1280×800 minimum (desktop only, không mobile P0)
- Network: test với 3G simulated cho slow query scenarios
D2) Requirements Matrix (FR → AC → Test ID)
FR-001: Dashboard 5 KPI cards
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-001-1 | Page load lần đầu < 2s với data có sẵn (staging seed 24k records 7 ngày × 70 CN) | AC-001-1 | Integration + Performance |
| TC-001-2 | Skeleton loading hiển thị TRƯỚC khi data load | AC-001-1 | Component |
| TC-001-3 | Filter change → KPI re-fetch với loading state intermediate | AC-001-2 | Integration |
| TC-001-4 | Delta indicator: positive → ▲green, negative → ▼red, no change → ─gray | AC-001-3 | Component |
| TC-001-5 | Hover ⓘ icon → tooltip hiển thị sau 300ms với content đúng theo B9 dictionary | AC-001-4 | Component |
| TC-001-6 | KPI #4 "Tuân thủ" mẫu số = 0 → hiển thị "—" không "0%"/"NaN%" | AC-001-5 | Component edge case |
| TC-001-7 | KPI #2 "Thời lượng TB" warning "X cuộc không có metadata" khi reference_file.duration IS NULL ở vài records | AC-001-6 | Integration |
FR-002: Trend Chart
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-002-1 | Hover 1 điểm → tooltip "Thứ X, DD/MM/YYYY: N cuộc" | AC-002-2 | Component |
| TC-002-2 | Click 1 điểm ngày 12/05 → navigate URL /e/record?dateExact=2026-05-12&branchId=<scope> | AC-002-3 | E2E |
| TC-002-3 | Period < 2 ngày data → empty state "Chưa đủ dữ liệu để vẽ xu hướng" | AC-002-4 | Component |
| TC-002-4 | Time range = 365 ngày → chart vẫn render với pagination/sampling phù hợp | AC-002-5 | Performance |
| TC-002-5 | Time range > 365 → input picker disable Apply | AC-002-5 + EdgeCase G4 | Component |
FR-003: Top Staff
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-003-1 | Hiển thị top 10 NV theo số cuộc, sorted desc | AC-003-1 | Integration |
| TC-003-2 | Click bar NV Lam → navigate /e/record?staffId=<lam_uuid>&from=&to=&branchId= | AC-003-2 | E2E |
| TC-003-3 | Empty state khi không có data | AC-003-3 | Component |
| TC-003-4 | Tên NV display dùng account.display_name (KHÔNG full_name) | (Pitfalls Map) | Integration |
FR-004: Heatmap
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-004-1 | Cell color theo amber scale chuẩn (0/1-3/4-7/8+) | AC-004-1 | Component |
| TC-004-2 | Hover cell → tooltip "Thứ X lúc Hh: N cuộc, TB Y phút" | AC-004-2 | Component |
| TC-004-3 | Click cell T5 10h → navigate /e/record?dateExact=<recent_T5> | AC-004-3 | E2E |
| TC-004-4 | Auto-hide giờ 22h-7h không có hoạt động | AC-004-4 | Component |
| TC-004-5 | Legend "Ít / Nhiều" hiển thị với color scale | AC-004-5 | Component |
FR-005: Branch Top + "Khác"
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-005-1 | Top 10 branches hiển thị với % tổng | AC-005-1 | Integration |
| TC-005-2 | Row "Khác (N CN)" gộp các CN ngoài top 10 | AC-005-2 | Component |
| TC-005-3 | Click bar Long Khánh → navigate /e/record?branchId=<lk_uuid> | AC-005-3 | E2E |
| TC-005-4 | CTA "[Xem tất cả 70 CN →]" hiển thị (defer P2 — visual only) | AC-005-4 | Component |
FR-006: Missing Records Modal
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-006-1 | Title dynamic "N lịch hẹn..." với N = count thực tế | AC-006-1 | Integration |
| TC-006-2 | Click CTA → modal mở với pagination 10 rows/page | AC-006-2 | E2E |
| TC-006-3 | Search box filter realtime → reset page 1 | AC-006-3 + DEC-013 | Component |
| TC-006-4 | Sort dropdown thay đổi order → re-fetch + reset page 1 | AC-006-3 | Component |
| TC-006-5 | Empty state khi count = 0 | AC-006-4 | Component |
| TC-006-6 | Click row [Mở] → tab mới /appointment/<id> | AC-006-2 | E2E |
| TC-006-7 | Page size selector 10/20/50 → re-fetch với limit phù hợp | DEC-013 | Component |
| TC-006-8 | Server-side pagination — không load tất cả records cùng lúc | DEC-013 | Integration + Performance |
| TC-006-9 | Modal P0 KHÔNG hiển thị Export Excel button (defer P2 theo DEC-014) | AC-006-3 + DEC-014 | Component |
FR-007: KH chờ TV Modal
Tương tự FR-006 nhưng với additional:
| Test ID | Mô tả test | AC ref |
|---|---|---|
| TC-007-1 đến TC-007-7 | Tương tự TC-006-1 đến TC-006-7 nhưng cho AnomalyMissingTVModal | AC-007-1 đến 4 |
| TC-007-8 | Filter "Lọc lý do" với 3 options (no-show / compliance gap / pending) | AC-007-3 |
| TC-007-9 | Row có 2 action: [Mở appt] + [KH] (tab mới khác nhau) | AC-007-4 |
FR-008: Filter Bar (70 CN + 700 NV)
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-008-1 | Branch dropdown — search "Long" → filter 1 results realtime | AC-008-1 | Component |
| TC-008-2 | Branch dropdown — "Chọn tất cả" → 70/70 selected | AC-008-1 | Component |
| TC-008-3 | Branch dropdown — group by region (nếu BE có) HOẶC flat list với search | AC-008-1 + PD-001 | Component |
| TC-008-4 | Date range picker — preset "7 ngày qua" → calc đúng (today - 6 days đến today) | AC-008-2 | Unit |
| TC-008-5 | Date range picker — custom range > 365 ngày → disable Apply | AC-008-2 + EdgeCase | Component |
| TC-008-6 | Staff dropdown — empty state mặc định (KHÔNG render 700 NV) | AC-008-3 | Component |
| TC-008-7 | Staff dropdown — type "lam" → debounce 300ms → fetch 10 results | AC-008-3 | Integration |
| TC-008-8 | Filter change → reset pagination về page 1 ở tất cả widget có pagination | AC-008-5 | Integration |
| TC-008-9 | BOD login với view_all → default 70 CN; BranchManager → branches của họ | AC-008-6 | Integration + Permission |
FR-009: Audio drill-down
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-009-1 | Click chart segment → navigate /e/record?<params> với params correct | AC-009-1 | E2E |
| TC-009-2 | /e/record parse query params và apply vào filter | AC-009-2 | Integration |
| TC-009-3 | Player HTML5 hoạt động không thay đổi (regression) | AC-009-3 | Regression |
| TC-009-4 | Browser back → quay lại dashboard với filter state giữ nguyên | AC-009-4 | E2E |
| TC-009-5 | File audio 404 → toast "File audio không khả dụng" (EdgeCase G7) | EdgeCase G7 | Integration |
FR-010: Permission v2
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-010-1 | BOD login → sidebar "Insight Ghi Âm" + button trên /e/record hiển thị | AC-010-1, 2 | E2E |
| TC-010-2 | Staff login → sidebar + button KHÔNG hiển thị | AC-010-1, 2 | E2E |
| TC-010-3 | Staff truy cập URL trực tiếp /e/record/insights → redirect /forbidden | AC-010-3 | E2E |
| TC-010-4 | ITLeader bypass branch filter (view_all) → thấy data 70 CN | AC-010-1 | Integration |
| TC-010-5 | Migration seed correct: row mới trong module_permission_action (module_id='voice_recording_management', action='view_insight'); BOD + ITLeader có 'view_insight' = ANY(role_module.actions) | AC-010-4 | Integration (DB query) |
| TC-010-6 | Admin (ROLE_MODERATOR) bypass tất cả checks | AC-010-5 | E2E |
| TC-010-7 | User mất quyền giữa session → next API 403 → toast + redirect | EdgeCase G3 | Integration |
| TC-010-8 (NEW PD-011) | JWT claim x-hasura-allowed-branches middleware — Decode JWT 3 role: BOD/ITLeader chứa list 70 branch UUIDs; BranchManager chứa own branch IDs; Staff (no view_insight) chứa [] empty array | PD-011 + G-PERM-6 | Integration (JWT decode test) |
| TC-010-9 (NEW PD-011) | Direct Hasura GraphQL query 3 materialized views: Staff token (claim []) → record_daily_summary query → trả 0 rows. BOD token (claim 70 IDs) → trả full rows | PD-011 + G-PERM-6 | Integration (Hasura security boundary) |
| TC-010-10 (NEW PD-011) | Hasura filter _in: [] empty branches behavior — verify PostgreSQL returns empty set (secure default) | PD-011 | Unit (Hasura/SQL) |
FR-011: /e/record extend
| Test ID | Mô tả test | AC ref | Loại |
|---|---|---|---|
| TC-011-1 | URL /e/record?staffId=X&from=Y&to=Z → filter pre-applied | AC-011-1 | E2E |
| TC-011-2 | URL ?dateExact=12/05/2026 → from = to = 12/05/2026 | AC-011-2 | E2E |
| TC-011-3 | URL ?durationGt=3600 → table filter records duration > 1h | AC-011-3 | Integration |
| TC-011-4 | URL không có params → RecordTable hoạt động như cũ (regression) | AC-011-4 | Regression |
D3) Dữ liệu mẫu (Seed Data) cho staging benchmark
D3.1) Required data
| Entity | Count | Note |
|---|---|---|
| Branch | 70 | Realistic distribution (28 TP HCM, 24 HN, 12 ĐN, 3 HP, 3 CT) |
| NV (account) | ~700 | 10 NV/branch trung bình |
| Customer | ~10,000 | Spread across branches |
| Appointment | ~50,000 | 30 ngày qua, mix status |
| Record | ~105,000 | 30 ngày × 3,500/day; mix duration (short < 1m, normal 5-15m, long > 60m) |
| Reference_file | ~105,000 | 1:1 với records (mostly); some NULL duration để test edge case |
D3.2) Permission seed
- 5 BOD users với role BOD + action
view_insight - 3 ITLeader users
- 1 Admin (
ROLE_MODERATOR) - 10 Branch Manager
- 50 Staff (test access deny)
D4) Ca kiểm thử (Test Cases) — Hành trình quan trọng (Critical Path E2E)
TC-CRITICAL-001: BOD happy path
- Login as BOD user
bod@diva.vn - Click sidebar "Insight Ghi Âm" → navigate
/e/record/insights - Verify dashboard load < 2s với data 7 ngày × 70 CN
- Verify 5 KPI cards hiển thị với số liệu + delta
- Verify 4 chart render
- Verify 2 anomaly cards với count
- Change filter: chọn "30 ngày qua" → loading state → re-fetch → render < 3s
- Change filter: chọn 3 chi nhánh cụ thể → loading → render
- Click Top Staff bar Nguyễn T. Lam → navigate
/e/record?staffId=<lam_uuid>&from=&to=&branchId=(open same tab) - Trên
/e/record, verify filter pre-applied + records hiển thị - Click play 1 record → audio modal mở → playback OK
- Browser back → quay lại dashboard với filter state intact
- Click anomaly 🔴 Missing → modal mở với pagination
- Search "Trần V. Hùng" trong modal → filter realtime → reset page 1
- Navigate page 2 → fetch + render
- Click [Mở] row → tab mới
/appointment/<id> - Close modal → quay về dashboard
TC-CRITICAL-002: Staff access deny
- Login as Staff user
- Verify sidebar KHÔNG có "Insight Ghi Âm"
- Verify
/e/recordKHÔNG có button [📊 Insight →] - Type URL
/e/record/insightstrong browser → redirect /forbidden - Verify error message phù hợp
TC-CRITICAL-003: Performance benchmark (DEC-012 P0 gate)
- Seed staging với 105k records × 70 CN × 30 ngày
- BOD login → mở dashboard với default filter (7 ngày)
- Verify TTFMP < 2s (Sentry transaction tracking)
- Change filter to 30 ngày → Verify TTFMP < 3s
- Run
EXPLAIN ANALYZEcho 7 GraphQL queries trong dev-spec C5 - Verify KHÔNG có Seq Scan trên
record/appointment(must use indexes) - Check Hasura cache hit rate qua admin console
- Verify URQL cache-and-network: lần 2 load < 200ms (cached)
D5) Entry / Exit Criteria
D5.1) Entry criteria (đủ điều kiện bắt đầu QA)
- ☐ All 11 FR implemented và unit tested (FE Dev report)
- ☐ Migrations chạy thành công trên staging (4 indexes + permission seed + 3 materialized views + Hasura YAML + scheduler)
- ☐ Seed data có sẵn (D3.1)
- ☐ 6 PD critical RESOLVED bởi BE TL + PO (đây là PREREQUISITE — KHÔNG được mark ✅ cho đến khi PD-status trong SOURCE_OF_TRUTH chuyển sang "Resolved"):
- PD-001 (region grouping schema)
- PD-003 (appointment baseline status/type/service_type/consultant_behavior)
- PD-005 (action name
view_insightconfirm) - PD-007 (
reference_file.urlpresigned hay raw) - PD-008 (performance benchmark plan trên staging)
- PD-011 (JWT claim
x-hasura-allowed-branchesBE middleware implemented + tested) — Block ship P0 BE security
- ☐ Cypress E2E setup hoạt động
D5.2) Exit criteria (đủ điều kiện ship P0)
- ✅ 100% TC-001 đến TC-011 PASS
- ✅ 100% TC-CRITICAL-001 đến TC-CRITICAL-003 PASS
- ✅ Performance gate: TTFMP < 2s/7d, < 3s/30d (TC-CRITICAL-003)
- ✅ Permission gate: 0 unauthorized access (TC-010, TC-CRITICAL-002)
- ✅ Regression: existing
/e/recordkhông break (TC-011-4 + manual smoke test) - ✅ Zero critical bugs (P0/P1 severity)
- ✅ Documentation review: BOD demo session OK
D5.3) Defect severity
| Severity | Định nghĩa | Block ship? |
|---|---|---|
| P0 Critical | Crash, data loss, security breach | YES |
| P1 High | Core flow broken, BOD không thể dùng | YES |
| P2 Medium | Edge case fail, UX issue | NO (ship + fix patch) |
| P3 Low | Minor visual, typo | NO |
D6) Performance Test Plan
D6.1) Load test scenarios
| Scenario | Concurrent users | Duration | Expected |
|---|---|---|---|
| Normal load | 10 BOD users | 30 min | TTFMP < 3s, no errors |
| Peak load | 30 users | 15 min | TTFMP < 5s, < 1% error rate |
| Stress test | 50 users (max) | 10 min | Identify bottleneck (DB CPU, Hasura, FE) |
D6.2) Tools
- Sentry transactions cho TTFMP tracking
- pg_stat_statements cho top slow queries
- Hasura admin cache hit metrics
- Lighthouse CI cho FE performance score
D7) Regression Suite (chạy mỗi release)
- TC-009-3:
/e/recordplayer HTML5 hoạt động - TC-011-4:
/e/recordkhông có URL params → behavior cũ /e/recorddownload ZIP + delete file flow (existing functionality)- Permission v2 không break các module khác đã dùng
hasPermissionAPI
Cross-ref:
- PRD:
./prd.mdA5 FR/AC- UI Spec:
./ui-spec.md- Dev Spec:
./dev-spec.mdC5 queries + C12 traceability- Go-Live:
./go-live-checklist.md