Appearance
QA Test Plan — Thu thập & Chuẩn hoá thông tin cá nhân khách hàng
Ref: PRD v1.0 | Date: 25/03/2026
D1) Test Scope
| FR | Mô tả | Priority | Platform |
|---|---|---|---|
| FR-001 | Consent Screen — fullscreen, dynamic content, checkbox | Must | Flutter |
| FR-002 | Popup đề xuất cập nhật TT — birthday + occupation + province | Must | Flutter |
| FR-003 | Logic retry popup — hỏi lại có kiểm soát | Must | Flutter |
| FR-004 | Tích hợp auth flow — check consent + update_info sau login | Must | Flutter |
| FR-005 | Track app_open_count | Must | Flutter |
| FR-006 | Admin: Config consent + profile update | Must | Admin Web |
| FR-007 | Admin: Thống kê % cập nhật | Must | Admin Web |
D2) Test Cases
TC-FR-001: Consent Screen
Happy path:
| TC | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|
| TC-001-01 | Khách mới thấy Consent Screen sau signup | Signup thành công, consent_config version 1 | Hiện fullscreen Consent Screen với title, body, 2 checkboxes (mặc định checked) | P0 |
| TC-001-02 | Bấm "Đồng ý & Tiếp tục" với cả 2 checkbox checked | Bấm CTA | Lưu customer_consent record: consent_data = {"marketing": true, "treatment_photo": true}, consent_version = 1. Navigate tiếp (popup hoặc dashboard) | P0 |
| TC-001-03 | Bỏ 1 checkbox, bấm CTA | Bỏ checkbox "Nhận thông tin khuyến mãi" | Lưu consent_data = {"marketing": false, "treatment_photo": true}. Vẫn vào app bình thường | P0 |
Beyond happy path:
| TC | Loại | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|---|
| TC-001-04 | Boundary | Bỏ cả 2 checkboxes | Bỏ tất cả checkbox, bấm CTA | Lưu consent_data = {"marketing": false, "treatment_photo": false}. Vẫn vào app | P1 |
| TC-001-05 | Negative | Kill app giữa Consent Screen | Force close app trước khi bấm CTA | Login sau hiện lại Consent Screen (chưa có record) | P1 |
| TC-001-06 | Negative | consent_config không tồn tại | Xoá consent_config khỏi app_setting | Skip Consent Screen, vào Dashboard bình thường | P1 |
| TC-001-07 | Negative | Mất mạng khi bấm CTA | Tắt wifi trước khi bấm | Hiện toast lỗi, cho thử lại. Không navigate | P1 |
| TC-001-08 | Navigation | Bấm back (Android) hoặc swipe back (iOS) | System back gesture | Không cho phép — vẫn ở Consent Screen | P1 |
| TC-001-09 | Link | Bấm "Chính sách bảo mật" | Tap link | Mở InAppWebView với URL từ app_setting.privacy_policy. Có nút Close quay lại | P2 |
TC-FR-002: Popup đề xuất cập nhật TT
Happy path:
| TC | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|
| TC-002-01 | Hiện popup 3 fields cho khách thiếu cả 3 | Khách chưa có birthday, occupation, province | Hiện popup với 3 fields: date picker, dropdown nghề nghiệp (4 options), dropdown tỉnh/thành | P0 |
| TC-002-02 | Cập nhật cả 3 fields | Chọn birthday 15/06/1990, occupation "Kinh doanh tự do", tỉnh "Hồ Chí Minh". Bấm "Cập nhật" | Lưu account.birthday, account.occupation, account_address.province_code. Set update_info_completed = true. Navigate Dashboard | P0 |
| TC-002-03 | Chỉ hiện fields thiếu | Khách đã có birthday, thiếu occupation + province | Popup chỉ hiện 2 fields: occupation + province. Không hiện birthday | P0 |
Beyond happy path:
| TC | Loại | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|---|
| TC-002-04 | Boundary | Chỉ cập nhật 1 field, bỏ 2 fields trống | Chỉ chọn birthday, bấm "Cập nhật" | Lưu birthday. Lần sau popup chỉ hỏi occupation + province | P1 |
| TC-002-05 | Boundary | Tất cả 3 fields đã có data | Khách đã có birthday + occupation + province_code | Không hiện popup, vào Dashboard trực tiếp | P0 |
| TC-002-06 | Negative | Bấm "Bỏ qua" | Bấm "Bỏ qua" | Tăng update_info_skip_count (+1). Navigate Dashboard. Không lưu data | P0 |
| TC-002-07 | Negative | Bấm nút X (close) | Bấm X góc trên phải | Tương tự bấm "Bỏ qua": tăng skip_count, navigate Dashboard | P1 |
| TC-002-08 | Combination | Cập nhật birthday hôm nay | Chọn ngày sinh = hôm nay (25/03/2026) | Cho phép lưu (không validate tuổi) | P2 |
| TC-002-09 | Combination | Chọn tỉnh/thành phố cuối danh sách | Scroll xuống cuối dropdown, chọn tỉnh | Lưu đúng province_code + province_name | P2 |
| TC-002-10 | Negative | Mất mạng khi bấm "Cập nhật" | Tắt wifi | Hiện toast lỗi, không navigate. Giữ data user đã nhập | P1 |
TC-FR-003: Logic retry popup
Happy path:
| TC | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|
| TC-003-01 | Hỏi lại sau 4 lần mở app (skip 1 lần) | Skip lần 1 (skip_count=1). Mở app lần 2, 3, 4 (không hiện). Mở app lần 5 (app_open_count=4) | Lần 5 hiện lại popup (4 >= 1*4) | P0 |
| TC-003-02 | Không hỏi nữa sau 3 lần skip | Skip 3 lần (skip_count=3, max_skip=3) | Không hiện popup nữa, bất kể mở app bao nhiêu lần | P0 |
Beyond happy path:
| TC | Loại | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|---|
| TC-003-03 | Boundary | Skip 2 lần, hỏi lại sau 8 lần mở | skip_count=2. Mở app 7 lần (không hiện). Mở lần 8 | Lần 8 hiện popup (8 >= 2*4) | P1 |
| TC-003-04 | Combination | Admin thay đổi max_skip từ 3 thành 1 | Khách đã skip 1 lần. Admin set max_skip=1. Khách mở app | Không hiện popup nữa (1 >= 1) | P2 |
| TC-003-05 | Combination | Admin thay đổi reshow_after_opens từ 4 thành 2 | Khách skip 1 lần. Admin set reshow_after_opens=2. Khách mở app lần 2 | Hiện popup (app_open >= 1*2) | P2 |
TC-FR-004: Tích hợp auth flow
Happy path:
| TC | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|
| TC-004-01 | Khách mới: full flow | Signup → auth success | Consent Screen → Profile Update Popup → Dashboard | P0 |
| TC-004-02 | Khách cũ chưa consent | Login → auth success. Chưa có customer_consent record | Consent Screen → Profile Update Popup → Dashboard | P0 |
| TC-004-03 | Khách đã consent + đủ data | Login. Có consent record (version current) + birthday + occupation + province | Thẳng Dashboard, không hiện gì | P0 |
Beyond happy path:
| TC | Loại | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|---|
| TC-004-04 | Combination | Admin tăng consent version | Khách có consent version=1. Admin tăng lên version=2 | Hiện Consent Screen lại (version cũ < current) | P0 |
| TC-004-05 | Combination | Event popup + consent | Khách cần consent + có event popup | Consent Screen → Profile Popup → Dashboard → Event Popup (sau dashboard) | P1 |
| TC-004-06 | Negative | API lỗi khi load consent | GetCustomerConsent trả lỗi | Skip consent flow, vào Dashboard. Hiện toast lỗi | P1 |
TC-FR-005: Track app_open_count
| TC | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|
| TC-005-01 | Increment mỗi lần mở app | Login 3 lần liên tiếp | app_open_count = 3 (tăng 1 mỗi lần) | P0 |
| TC-005-02 | Không increment nếu chưa consent | Login nhưng chưa có customer_consent record | app_open_count không tồn tại (chưa có record) | P1 |
TC-FR-006: Admin Config
Happy path:
| TC | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|
| TC-006-01 | Sửa consent title + bấm "Lưu" | Sửa title → bấm "Lưu" | app_setting cập nhật, version giữ nguyên. Khách KHÔNG bị hỏi lại | P0 |
| TC-006-02 | Bấm "Lưu & Tăng version" | Bấm nút → hiện dialog xác nhận → confirm | version++ trong app_setting. Khách phải consent lại | P0 |
| TC-006-03 | Sửa occupation options | Thêm option "Nội trợ" vào danh sách | app_setting cập nhật. Flutter app hiện option mới trong dropdown | P0 |
Beyond happy path:
| TC | Loại | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|---|
| TC-006-04 | Negative | Bấm "Lưu & Tăng version" rồi huỷ dialog | Bấm nút → hiện dialog → bấm "Huỷ" | Không lưu, version không đổi | P1 |
| TC-006-05 | Boundary | Toggle tắt popup đề xuất | Tắt toggle "Bật popup đề xuất" → Lưu | profile_update_info.enabled = false. Flutter app skip popup | P1 |
| TC-006-06 | Combination | Sửa max_skip thành 0 | Set max_skip = 0 → Lưu | Popup không bao giờ hiện (0 >= 0 ngay lập tức) | P2 |
TC-FR-007: Admin Thống kê
| TC | Mô tả | Input | Expected | Priority |
|---|---|---|---|---|
| TC-007-01 | Hiển thị 5 metrics | Mở tab Consent trên admin | Hiển thị: Tổng khách, Đã consent (%), Có birthday (%), Có occupation (%), Có tỉnh/thành (%) | P0 |
| TC-007-02 | % tính đúng | 100 khách, 70 đã consent | Đã consent: 70 (70.0%) | P1 |
| TC-007-03 | Refresh khi mở tab | Đóng/mở tab Consent | Data load mới, không cache | P2 |
D3) Seed Data
Dataset: DS-001 — Test accounts cho consent flow
Cách tạo: SQL Script
sql
-- Tạo 5 test accounts với trạng thái khác nhau
-- Account 1: Khách mới, chưa có gì
INSERT INTO account (id, display_name, phone_code, phone_number, role, gender)
VALUES ('test-consent-01', 'Nguyễn Thị Mới', 84, '0901000001', 'customer', 'female');
-- Account 2: Khách đã consent, chưa cập nhật TT
INSERT INTO account (id, display_name, phone_code, phone_number, role, gender)
VALUES ('test-consent-02', 'Trần Văn Cũ', 84, '0901000002', 'customer', 'male');
INSERT INTO customer_consent (account_id, branch_id, consent_data, consent_version, app_open_count)
VALUES ('test-consent-02', 'test-branch-id', '{"marketing": true, "treatment_photo": true}', 1, 0);
-- Account 3: Khách đã consent + skip 1 lần, mở app 3 lần
INSERT INTO account (id, display_name, phone_code, phone_number, role, gender)
VALUES ('test-consent-03', 'Lê Thị Skip', 84, '0901000003', 'customer', 'female');
INSERT INTO customer_consent (account_id, branch_id, consent_data, consent_version, update_info_skip_count, app_open_count)
VALUES ('test-consent-03', 'test-branch-id', '{"marketing": true, "treatment_photo": false}', 1, 1, 3);
-- Account 4: Khách đã cập nhật đủ thông tin
INSERT INTO account (id, display_name, phone_code, phone_number, role, gender, birthday, occupation)
VALUES ('test-consent-04', 'Phạm Văn Đủ', 84, '0901000004', 'customer', 'male', '1990-06-15', 'office_worker');
INSERT INTO customer_consent (account_id, branch_id, consent_data, consent_version, update_info_completed, app_open_count)
VALUES ('test-consent-04', 'test-branch-id', '{"marketing": true, "treatment_photo": true}', 1, true, 5);
INSERT INTO account_address (account_id, province_code, province_name, primary_contact)
VALUES ('test-consent-04', '79', 'Hồ Chí Minh', true);
-- Account 5: Khách skip 3 lần (max) — không hỏi nữa
INSERT INTO account (id, display_name, phone_code, phone_number, role, gender)
VALUES ('test-consent-05', 'Hoàng Thị Max', 84, '0901000005', 'customer', 'female');
INSERT INTO customer_consent (account_id, branch_id, consent_data, consent_version, update_info_skip_count, app_open_count)
VALUES ('test-consent-05', 'test-branch-id', '{"marketing": true, "treatment_photo": true}', 1, 3, 15);
-- Verify
SELECT a.id, a.display_name, a.birthday, a.occupation,
cc.consent_version, cc.update_info_skip_count, cc.update_info_completed, cc.app_open_count,
aa.province_code
FROM account a
LEFT JOIN customer_consent cc ON cc.account_id = a.id
LEFT JOIN account_address aa ON aa.account_id = a.id AND aa.primary_contact = true
WHERE a.id LIKE 'test-consent-%'
ORDER BY a.id;D4) Traceability
| FR | TC-ID | Coverage | Status |
|---|---|---|---|
| FR-001 | TC-001-01 ~ TC-001-09 | 9 test cases (3 happy + 6 beyond) | |
| FR-002 | TC-002-01 ~ TC-002-10 | 10 test cases (3 happy + 7 beyond) | |
| FR-003 | TC-003-01 ~ TC-003-05 | 5 test cases (2 happy + 3 beyond) | |
| FR-004 | TC-004-01 ~ TC-004-06 | 6 test cases (3 happy + 3 beyond) | |
| FR-005 | TC-005-01 ~ TC-005-02 | 2 test cases | |
| FR-006 | TC-006-01 ~ TC-006-06 | 6 test cases (3 happy + 3 beyond) | |
| FR-007 | TC-007-01 ~ TC-007-03 | 3 test cases | |
| Tổng | 41 test cases |
D5) Entry / Exit Criteria
Entry (bắt đầu test)
- [ ] BE deploy xong: migration
customer_consent+ seed data + Hasura metadata (track table + permissions) - [ ] Hasura metadata:
public_account.yamlđã sửa (customer update birthday, occupation) - [ ] FE Web deploy xong trên test env (admin tab Consent)
- [ ] Flutter build deploy xong trên test device/emulator
- [ ] Seed data DS-001 đã chạy trên test DB
- [ ] Test accounts có token/login credentials
- [ ] consent_config + profile_update_info đã có trong app_setting
Exit (kết thúc test)
- [ ] Tất cả P0 test cases PASS (19 TCs)
- [ ] Tất cả P1 test cases PASS hoặc có waiver từ PO (15 TCs)
- [ ] P2 test cases: document known issues nếu FAIL (7 TCs)
- [ ] Performance: GetCustomerConsent < 100ms, ConsentStats < 500ms
- [ ] No critical/major bugs open
- [ ] Cross-platform test: Android + iOS cho Flutter screens