Giao diện
US-S01: Nhập fixture và chạy một case (gọi API · SQL · hướng dẫn, tự chấm, ghi ngược fixture) ​
Thuộc FS: 00-overview.md — SAPP-26297 Chuyển nhượng học viên (tool
sapp.011) Phần chung (tổng quan, roles, flow tổng, related features, NFR, Open Questions, References) xem ở Overview. File này chỉ chứa nội dung riêng của US.US khung chạy. Mô tả cơ chế dùng chung cho mọi case của 9 tab. Các US luồng (US-S02 → US-S10) chỉ mô tả case + kỳ vọng tự chấm và tham chiếu file này cho cơ chế.
| Field | Value |
|---|---|
| US ID | US-S01 |
| Role thực hiện | R1 - Người dùng tool — Assumption (OQ về G-01 → OQ-S-01) |
| Phase | As-built (đã triển khai @ e77c47a) |
| Priority | N/A — as-built |
| Status | Draft |
| NFR liên quan | NFR-S-01, NFR-S-02, NFR-S-03, NFR-S-04, NFR-S-05 — xem Overview §5 |
| OQ liên quan | OQ-S-01, OQ-S-06, OQ-S-07, OQ-S-11, OQ-S-15, OQ-S-18, OQ-S-19 — xem Overview §6 |
1. US Statement ​
text
As a Người dùng tool [Assumption — OQ-S-01]
I want nhập một bộ id dùng chung (fixture), chọn môi trường dev/staging rồi chạy từng bước của một case
(gọi API Ops / LMS / HubSpot, chạy câu SQL đã lập danh mục, đọc hướng dẫn tay) và xem kết quả tự chấm
So that tool "gọi đúng và chấm đúng" hành vi hiện có của SAPP-26297
(nguồn: shared/external/sapp/sapp-26297/2026-09-25-mind-sapp-26297-cau-hoi.md — lời mở đầu;
mục tiêu nghiệp vụ rộng hơn là [Assumption] — OQ-S-18)2. Dependencies & Definition Of Ready ​
N/A — đã triển khai. Dependency runtime (để màn chạy được):
| # | Cần sẵn sàng | Ai cung cấp | Trạng thái |
|---|---|---|---|
| D1 | Row tool sapp.011 trong bảng tools (migration seed) — thiếu ⇒ màn bị khoá (US-P02) | BE / DevOps | ❓ OQ-S-11 |
| D2 | Credentials Ops SAPP_OPS_* trong env_systems cho auto-login (US-P04, US-P08) | Admin CMS | Fact: có dùng (be/libs/modules/src/lib/sapp/sapp-26297.common.service.ts:62-73) |
| D3 | Catalogue SQL s26297.* có đủ trên cả FE và BE (NFR-S-05); SQL runner hoạt động (US-P09) | BE + FE | Fact: 39 entry khớp tại e77c47a |
3. Business Rules ​
| ID | Rule | Áp dụng ở | Đã cover bởi (AC) | Nguồn code (Mode B/C) |
|---|---|---|---|---|
| BR-01 | Màn chỉ mở khi tool sapp.011 có trong DB mind_keep và đang mở cho viewer (cơ chế ở US-P02) | M1 - vào màn | AC-M1.1 | be/libs/database/migrations/1786300000000-seed-tool-sapp-26297.ts:6-8 · fe/src/ui/pages/works/sapp/tools/shell.tsx:427-435 |
| BR-02 | Chỉ có 2 môi trường dev và staging, mặc định dev; mọi bước gọi route của môi trường đang chọn; không có lựa chọn production (NFR-S-02) | M1 - chọn môi trường | AC-M1.2 | fe/src/ui/components/works/sapp/sapp-26297-tool.tsx:179,222-234 · be/apps/api/src/sapp/sapp-26297.controller.ts:39-40 |
| BR-03 | Luôn hiện banner đỏ cảnh báo: mọi nút "Chạy (GHI)" sửa thẳng dữ liệu Ops và HubSpot của môi trường đang chọn; dùng học viên test riêng; không có route production | M1 - banner | AC-M1.2 | sapp-26297-tool.tsx:238-244 |
| BR-04 | Fixture được lưu trên trình duyệt này, trừ token đăng nhập (chỉ sống trong bộ nhớ tới khi đóng tab, ô kiểu password — NFR-S-03). Truy cập lưu trữ lỗi (cửa sổ riêng tư…) thì màn vẫn chạy, chỉ không nhớ fixture | M1 - fixture | AC-M1.3, EC-M1.2 | sapp-26297-tool.tsx:25-56 · fe/src/ui/components/works/sapp/sapp-26297-flows.ts:29-33 |
| BR-05 | Ô fixture hiện theo tab: tab "Chuẩn bị · Dọn" hiện tất cả; ô không gắn luồng thuộc tab 1, 2, 3A, 3B, 3C, 4; ô gắn luồng chỉ hiện ở luồng đó | M1 - fixture | AC-M1.4 | sapp-26297-tool.tsx:195-201 · sapp-26297-flows.ts:36-191 |
| BR-06 | Giá trị fixture được cắt khoảng trắng; ô rỗng không được đưa vào bước (để không ghi đè giá trị tester gõ trong panel SQL) | M1 → M2, M3 | AC-M2.1 | sapp-26297-tool.tsx:58-71 |
| BR-07 | Mỗi tab hiện tóm tắt luồng và danh sách case ở dạng đóng (mã · tên · "Kỳ vọng: …"); bấm "Mở" thì hiện cảnh báo nổi bật (nếu case có) rồi lần lượt các bước; bấm "Thu gọn" để đóng | M1 - case | AC-M1.5 | sapp-26297-tool.tsx:120-176,271-304 |
| BR-08 | Đổi môi trường làm mới mọi bước đang hiện (kết quả chạy và ô đã sửa tay bị bỏ) | M1 - chọn môi trường | AC-M1.6 | sapp-26297-tool.tsx:163-170 |
| BR-09 | Ô field của bước gọi API được điền sẵn từ fixture qua token {key}; ô tester đã sửa giữ giá trị gõ tay, ô chưa sửa luôn lấy lại theo fixture hiện tại | M2 - fields | AC-M2.1 | fe/src/ui/components/works/sapp/sapp-26297-step-call.tsx:417-427 |
| BR-10 | Nút Chạy bị khoá khi còn ô chứa token {…} chưa được fixture thay; màn hiện "Chưa điền: <danh sách ô>. Điền ở fixture hoặc sửa trực tiếp ô trên." | M2 - action Chạy | AC-M2.2 | sapp-26297-step-call.tsx:428-430,490-495,541-547 |
| BR-11 | Ô để trống = không gửi field đó (cách dựng case thiếu field); các ô định danh đường dẫn (lớp, học viên), token LMS và id bản ghi HubSpot không bao giờ vào body / query | M2 - fields | AC-M2.3 | sapp-26297-step-call.tsx:96-116 · sapp-26297-flows.ts:221-224 |
| BR-12 | Preview request luôn hiện: GET → đường dẫn + query; xoá học viên → body có danh sách học viên; thao tác khác → body JSON hoặc "(không body)" | M2 - preview | AC-M2.3 | sapp-26297-step-call.tsx:191-204,528-530 |
| BR-13 | 6 thao tác được coi là GHI — chuyển nhượng, hủy chuyển nhượng, xoá học viên, tạo contact, tạo ticket, sửa ticket — có nhãn "Chạy (GHI)" và hộp xác nhận kèm preview, ghi "không có undo"; chọn huỷ thì không gọi | M2 - action Chạy (GHI) | AC-M2.4 | sapp-26297-flows.ts:211-219 · sapp-26297-step-call.tsx:431-441,500-502 |
| BR-14 | Với thao tác Ops và LMS, mọi câu trả lời của hệ đích — kể cả từ chối 4xx — được trả nguyên văn (mã HTTP đích + body) để chấm đúng mã lỗi | M2 - kết quả | AC-M2.5 | sapp-26297.controller.ts:31-41,65-76 · sapp-26297.common.service.ts:30-40,90-93 |
| BR-15 | Chỉ khi không gọi được hệ đích (mạng, timeout) mới báo lỗi 13042 "Failed to call SAPP Ops API" kèm lý do; thao tác HubSpot bị HubSpot từ chối ⇒ lỗi của mind_keep (fixture hỏng, không phải kết quả cần chấm) | M2 - lỗi | AC-M2.6 | sapp-26297.common.service.ts:75-88,217-225 · sapp-26297.controller.ts:51-63 · be/libs/constants/src/lib/error-codes/error-codes.constants.ts:750-754 |
| BR-16 | Mind_keep không kiểm tra field nào của payload Ops — mọi field tuỳ chọn để request sai cố ý tới được Ops; giá trị query rỗng bị bỏ trước khi gửi | M2 - gọi API | AC-M2.3 | be/libs/interface/src/lib/dto/sapp/sapp-26297.dto.ts:8-14 · sapp-26297.common.service.ts:96-103 |
| BR-17 | Tự chấm: sau khi chạy, mỗi check là một dòng ✓/✗ kèm giá trị thực; tổng "x/y đạt" (xanh khi đạt hết, đỏ khi thiếu); bước không có check thì không hiện tổng; response thô trong khối thu gọn "Response Ops — HTTP n" | M2 - kết quả | AC-M2.5 | sapp-26297-step-call.tsx:461-483,555-583 |
| BR-18 | Loại check và cách chấm theo bảng "Loại check" dưới M2 | M2 - kết quả | AC-M2.5, AC-M2.7 | sapp-26297-step-call.tsx:251-395 |
| BR-19 | Hai mẫu chấm dùng lại nhiều nhất: "Từ chối đúng" = HTTP Ops 400 và mã lỗi = mã mong đợi (dạng 400|<mã>) và thông điệp = exceptions.<khoá>; "Cập nhật thành công" = HTTP 200, success = true, data.message = "Update successfully" | M2 - kết quả | AC-M2.7 | sapp-26297-flows.ts:387-403 |
| BR-20 | Ghi ngược fixture (produces): bước khai báo produces hiện dòng "Chạy xong sẽ ghi vào fixture: …"; chạy xong, giá trị đọc được (khác rỗng / null) được ghi vào fixture và lưu. Có 3 khoá: contact_id ← contact.id (tạo contact), ticket_id ← id (tạo ticket), hs_user_id ← cột user_id (SQL s26297.hs5.1) | M2, M3 | AC-M2.8 | sapp-26297-step-call.tsx:449-458,532-539 · sapp-26297-flows.ts:1550,1592,1636 |
| BR-21 | Bước SQL chỉ chạy câu có sẵn trong catalogue s26297.* theo id, trên DB Ops của môi trường đang chọn; câu GHI phải xác nhận trước (cơ chế runner ở US-P09) | M3 | AC-M3.1, AC-M3.2 | sapp-26297-tool.tsx:96-107 · fe/src/ui/components/works/sapp/sapp-26878-step-sql.tsx:203-213 · be/libs/constants/src/lib/sapp/sapp-ops-sql.constants.ts:7-13 |
| BR-22 | So sánh trạng thái trong SQL dùng chuỗi lưu thật trong DB Ops: TRANSFERED_TO (một chữ R), BE_TRANSFERRED | M3 | AC-M3.1 | sapp-26297-flows.ts:10-11 · be/libs/constants/src/lib/sapp/sapp-26297-sql.constants.ts:13-14 |
| BR-23 | Bước hướng dẫn chỉ hiển thị tiêu đề và danh sách dòng chữ, không gọi hệ thống nào | M4 | AC-M4.1 | sapp-26297-tool.tsx:109-117 |
4. Design ​
a. Mối liên hệ giữa các màn & User Flow ​
text
[Mở màn sapp-26297] (ToolGate → US-P02)
↓
[M1: Màn tool] — chọn dev|staging · đọc banner · nhập fixture · chọn tab · bấm "Mở" một case
↓ lần lượt từng bước của case
├─ bước call ──→ [M2: Bước gọi API] → Chạy / Chạy (GHI) → ✓/✗ + x/y đạt → (ghi fixture)
├─ bước sql ──→ [M3: Bước SQL] → chạy câu theo id → kết quả (US-P09) → (ghi fixture)
└─ bước guide ──→ [M4: Bước hướng dẫn] → đọc, làm tayb. Chi tiết từng màn ​
Màn M1 - Màn tool SAPP-26297 ​
| Field | Value |
|---|---|
| Figma frame | N/A |
| Trigger vào màn | Mở /works/sapp/tools/sapp-26297 (sidebar nhóm "SAPP 26297 - Chuyển nhượng học viên", mục "Test theo luồng") |
| Mô tả ngắn | Tiêu đề "SAPP-26297 Chuyển nhượng học viên", mô tả "Test trên nhánh sapp-sprint-49 · luồng 1–6 của guide QA · gọi Ops, LMS, HubSpot thật và tự chấm kết quả." Chọn môi trường, fixture, 9 tab luồng, danh sách case |
UI States ​
| State | Khi nào | Hiển thị | Figma frame |
|---|---|---|---|
| Default | Vừa vào màn | Môi trường dev được chọn; tab "Chuẩn bị · Dọn" được chọn; fixture nạp từ trình duyệt (token để trống); banner đỏ cảnh báo GHI; tóm tắt luồng + 2 case đóng | N/A |
| Loading | N/A — màn không tải dữ liệu khi vào; mỗi bước có trạng thái riêng (M2, M3) | — | N/A |
| Has data | Chọn một tab | Tóm tắt luồng + danh sách case của tab (mã · tên · "Kỳ vọng: …" · "Mở") | N/A |
| Empty | N/A — mọi tab đều có case cố định (78 case) | — | N/A |
| Error | Id SQL mà case tham chiếu thiếu trong catalogue FE | Màn lỗi khi tải (NFR-S-05) | N/A |
| Disabled | Tool sapp.011 không có / bị khoá | Màn bị ToolGate chặn (US-P02) | N/A |
Fields ​
| Field | Required | Type | Default | Format | Validation | Error Message | Visibility | Depends On |
|---|---|---|---|---|---|---|---|---|
| Môi trường | Yes | Chip chọn 1 | dev | dev | staging | BR-02 | — | Always | — |
Lớp L1 / L2 / F1 (class_id_l1, class_id_l2, foundation_class_id), subject_id, học viên A / B / C (user_a, user_b, user_c) | Theo bước dùng tới | Text | Rỗng / giá trị đã lưu | id Ops | BR-06 | — | Tab Chuẩn bị + 1, 2, 3A, 3B, 3C, 4 (BR-05) | — |
Nhóm 3B: class_id, user_x, user_y | Theo bước | Text | Rỗng | id Ops | BR-06; ⚠️ Observed: user_y trống thì lấy user_x (EC-M1.3) | — | Chuẩn bị + 3B | — |
Token LMS lms_token_a, lms_token_b, lms_token | Luồng 4 | Password | Rỗng (không lưu) | token học viên | BR-04, NFR-S-03 | — | Chuẩn bị + 4 | — |
lms_user_id, lms_class_id | Luồng 4 hồi quy | Text | Rỗng | id | BR-06 | — | Chuẩn bị + 4 | — |
HubSpot: hs_pipeline_id, hs_stage_open_id, hs_stage_close_id, hs_owner_id, khu_vuc, contact_email, class_code_old, class_code_1, class_code_2 | Luồng 5–6 | Text | Rỗng | khu_vuc: từ thứ 2 là ACCA / CFA / CMA / Cert / Dip / CGMA (gợi ý) | BR-06 | — | Chuẩn bị + 5, 6 | — |
contact_id, ticket_id, hs_user_id | Luồng 5–6 | Text | Rỗng | id | BR-20 (tự điền khi chạy bước) | — | Chuẩn bị + 5, 6 | Bước tạo contact / tạo ticket / SQL hs5 |
Mỗi ô hiện nhãn + token
{key}để tester biết bước nào dùng nó. Nguồn:sapp-26297-flows.ts:36-191·sapp-26297-tool.tsx:246-269.
Actions ​
A1 - Chọn môi trường ​
| Item | Description |
|---|---|
| Trigger | Bấm chip dev hoặc staging |
| Behavior | Đổi môi trường cho mọi bước; làm mới các bước đang hiện (BR-02, BR-08). Không ghi dữ liệu |
| Success Result | Chip được chọn sáng; banner nêu môi trường mới |
Error cases:
| Case | Expected Handling |
|---|---|
| N/A — thao tác thuần giao diện | — |
A2 - Nhập / sửa fixture ​
| Item | Description |
|---|---|
| Trigger | Gõ vào một ô fixture |
| Behavior | Cập nhật fixture và lưu trình duyệt ngay (trừ token) (BR-04). Ghi: localStorage trình duyệt, khoá sapp-26297:fixture |
| Success Result | Mọi bước chưa sửa tay lấy giá trị mới (BR-09) |
Error cases:
| Case | Expected Handling |
|---|---|
| Trình duyệt chặn lưu trữ | Bỏ qua lỗi, màn vẫn chạy, fixture chỉ sống trong phiên (EC-M1.2) |
A3 - Chọn tab luồng / mở case ​
| Item | Description |
|---|---|
| Trigger | Bấm tab (Chuẩn bị · Dọn, 1 · Chuyển nhượng, 2 · Hủy chuyển nhượng, 3A, 3B, 3C, 4 · LMS, 5 · HubSpot đồng bộ lại, 6 · Mail); bấm "Mở" / "Thu gọn" trên case |
| Behavior | BR-05, BR-07 |
| Success Result | Hiện tóm tắt + case; case mở hiện cảnh báo nổi bật (nếu có) và các bước |
Error cases:
| Case | Expected Handling |
|---|---|
| N/A | — |
Acceptance Criteria của màn M1 ​
AC-M1.1 - Màn chỉ mở khi tool được bật ​
gherkin
Given DB mind_keep có row tool "sapp.011" đang mở cho viewer
When người dùng mở /works/sapp/tools/sapp-26297
Then màn hiện tiêu đề "SAPP-26297 Chuyển nhượng học viên"
And khi thiếu row hoặc tool bị khoá thì màn bị chặn theo US-P02Maps to:
- Business Rule: BR-01
- Field/Action: vào màn
AC-M1.2 - Mặc định dev, chỉ dev/staging, banner cảnh báo ​
gherkin
Given người dùng vừa mở màn
Then chip "dev" đang được chọn và chỉ có 2 chip "dev", "staging"
And banner đỏ ghi mọi nút "Chạy (GHI)" sửa thẳng dữ liệu trên Ops và HubSpot dev, và "Không có route production"
When người dùng bấm "staging"
Then banner đổi thành "... Ops và HubSpot staging ..."Maps to:
- Business Rule: BR-02, BR-03
- Field/Action: A1
AC-M1.3 - Fixture được nhớ, token thì không ​
gherkin
Given người dùng đã nhập user_a = "U1" và lms_token_a = "T1"
When người dùng tải lại trang
Then ô user_a vẫn là "U1"
And ô lms_token_a trốngMaps to:
- Business Rule: BR-04
- Field/Action: A2
AC-M1.4 - Ô fixture hiện theo tab ​
gherkin
Given tab "Chuẩn bị · Dọn" đang chọn
Then mọi ô fixture đều hiện
When người dùng chọn tab "1 · Chuyển nhượng"
Then chỉ hiện các ô không gắn luồng (lớp L1/L2/F1, subject, học viên A/B/C)
When người dùng chọn tab "5 · HubSpot đồng bộ lại"
Then chỉ hiện các ô HubSpot của luồng 5Maps to:
- Business Rule: BR-05
AC-M1.5 - Mở case ​
gherkin
Given tab "2 · Hủy chuyển nhượng" đang chọn
Then mỗi case hiện mã, tên, "Kỳ vọng: ..." và nút "Mở"
When người dùng bấm "Mở" trên case 2.7
Then hiện khung cảnh báo vàng nội dung highlight của case
And hiện lần lượt các bước của case theo thứ tự
And nút đổi thành "Thu gọn"Maps to:
- Business Rule: BR-07
- Field/Action: A3
AC-M1.6 - Đổi môi trường làm mới bước ​
gherkin
Given một bước gọi API đã chạy xong ở dev và có kết quả ✓/✗
When người dùng bấm "staging"
Then bước đó trở về trạng thái chưa chạy, ô sửa tay trở về giá trị theo fixtureMaps to:
- Business Rule: BR-08
Negative AC / Edge Cases của màn M1 ​
| ID | Tình huống | Hành vi mong đợi |
|---|---|---|
| EC-M1.1 | Mở 2 tab trình duyệt cùng lúc | Fixture ở localStorage dùng chung; tab nào ghi sau thì thắng (không có đồng bộ giữa tab). Token chỉ ở tab đã nhập |
| EC-M1.2 | Cửa sổ riêng tư / trình duyệt chặn lưu trữ | Lỗi lưu trữ bị nuốt; màn chạy bình thường, fixture không được nhớ khi tải lại |
| EC-M1.3 | ⚠️ Observed — user_y để trống | Hành vi hiện tại: tool tự lấy user_x cho user_y (sapp-26297-tool.tsx:69); hệ quả ở case 3.10 xem US-S06 — OQ-S-04 |
| EC-M1.4 | Refresh giữa lúc bước đang chạy | Kết quả bước mất (trạng thái chạy chỉ trong bộ nhớ); thao tác GHI đã gửi vẫn có hiệu lực trên hệ đích |
Màn M2 - Bước gọi API (call) ​
| Field | Value |
|---|---|
| Figma frame | N/A |
| Trigger vào màn | Mở một case có bước loại call |
| Mô tả ngắn | Khung có tiêu đề bước, ghi chú, các ô field, preview request, nút "Chạy" / "Chạy (GHI)", kết quả tự chấm và response thô |
UI States ​
| State | Khi nào | Hiển thị | Figma frame |
|---|---|---|---|
| Default | Chưa chạy | Ô field (placeholder "(bỏ trống = không gửi)"), preview, nút Chạy; nếu có produces thì dòng "Chạy xong sẽ ghi vào fixture: …" | N/A |
| Loading | Đang gọi | Nút "Đang chạy…" bị khoá | N/A |
| Has data | Gọi xong | Danh sách check ✓/✗ + giá trị thực; "x/y đạt"; khối thu gọn "Response Ops — HTTP n" | N/A |
| Empty | Bước không có check (vd pipeline, owner) | Chỉ có response thô, không có tổng đạt | N/A |
| Error | Route mind_keep trả lỗi | Hộp đỏ "Không gọi được Ops qua API: <status> <code> <message>" | N/A |
| Disabled | Còn ô chứa {token} chưa thay | Nút khoá, tooltip + dòng vàng "Chưa điền: …" | N/A |
Fields ​
| Field | Required | Type | Default | Format | Validation | Error Message | Visibility | Depends On |
|---|---|---|---|---|---|---|---|---|
Ô field của bước (tên field API, vd transfer_from, page_index, lop_moi) | Theo case | Text (token LMS: Password) | Template của bước, điền từ fixture | Tuỳ field | BR-10 (còn {…} ⇒ khoá); BR-11 (trống ⇒ không gửi) | "Chưa điền: <ô>…" | Always | Fixture (BR-09) |
Actions ​
A1 - Chạy / Chạy (GHI) ​
| Item | Description |
|---|---|
| Trigger | Bấm "Chạy" (thao tác đọc) hoặc "Chạy (GHI)" (6 thao tác ghi) |
| Behavior | Thao tác GHI: hộp xác nhận "Lệnh này GHI dữ liệu trên Ops <env> và không có undo" + preview (BR-13). Gọi route /api/v1/sapp/<env>/sapp-26297/… (danh mục dưới). Ops/LMS: nhận nguyên văn (BR-14). Chấm (BR-17, BR-18). Ghi ngược fixture (BR-20). Ghi vào: Ops dev/staging (chuyển nhượng, hủy, xoá mềm học viên) hoặc HubSpot (contact, ticket) — theo môi trường chọn. Ops / LMS không có đường PROD (NFR-S-02). ⚠️ Ghi PROD: thao tác GHI HubSpot ở staging ghi vào portal HubSpot production (NFR-S-02 ngoại lệ, EC-M2.6, OQ-S-19) |
| Success Result | Kết quả chấm hiện dưới bước |
Danh mục 14 thao tác (route {env} = dev | staging):
| Thao tác | Route mind_keep | Hệ đích | GHI? |
|---|---|---|---|
| Ứng viên nhận chuyển nhượng | GET {env}/sapp-26297/classes/:classId/transfer-candidates | Ops | Không |
| Chuyển nhượng | PUT {env}/sapp-26297/classes/:classId/students/transfer | Ops | Có |
| Bản ghi chuyển nhượng hiệu lực | GET {env}/sapp-26297/classes/:classId/students/:studentId/transfer-record | Ops | Không |
| Hủy chuyển nhượng | PUT {env}/sapp-26297/classes/:classId/students/:studentId/revert-transfer | Ops | Có |
| Danh sách học viên lớp | GET {env}/sapp-26297/classes/:classId/students | Ops | Không |
| Xoá học viên khỏi lớp | DELETE {env}/sapp-26297/classes/student | Ops (xoá mềm) | Có |
| Khoá học của tôi (LMS) | GET {env}/sapp-26297/lms/courses + header X-Lms-Token | LMS | Không |
| Tạo contact | POST {env}/sapp-26297/hubspot/contacts | HubSpot | Có |
| Đọc contact | GET {env}/sapp-26297/hubspot/contacts/:contactId | HubSpot | Không (không bước nào dùng — OQ-S-08) |
| Tạo ticket | POST {env}/sapp-26297/hubspot/tickets | HubSpot | Có |
| Sửa ticket (kể cả kéo stage) | PATCH {env}/sapp-26297/hubspot/tickets/:ticketId | HubSpot | Có |
| Đọc ticket | GET {env}/sapp-26297/hubspot/tickets/:ticketId | HubSpot | Không |
| Pipeline ticket | GET {env}/sapp-26297/hubspot/ticket-pipelines | HubSpot | Không |
| Owner | GET {env}/sapp-26297/hubspot/owners | HubSpot | Không |
Nguồn: sapp-26297.controller.ts:78-629 · fe/src/data/constants/api.ts:339-376 · sapp-26297-flows.ts:211-219.
Loại check (BR-18):
| Loại | Đạt khi |
|---|---|
| status | Mã HTTP của hệ đích = giá trị mong đợi |
| error-code / error-message | error.code / error.message trong body = giá trị mong đợi |
| replacements-keys | Mọi khoá liệt kê trong error.replacements có giá trị khác rỗng / null |
| data-null | data = null |
| path-equals / path-present | Giá trị tại đường dẫn = giá trị mong đợi (điền từ fixture) / có giá trị |
| list-has-user (± phủ định) | Đường dẫn là mảng và có (không có) phần tử là học viên: chuỗi = id, hoặc object có user_id / id / user.id = id |
| list-has-class | Mảng có phần tử là lớp: chuỗi = id hoặc object có class_id / id = id |
| list-empty (± phủ định) | Mảng rỗng (không rỗng) |
| candidate-eligible | Người nhận có trong data.data, lớp nhận có trong eligible_classes của người đó và progress = 0 |
| body-contains (± phủ định) | Chuỗi id xuất hiện (không xuất hiện) nguyên chuỗi ở bất kỳ đâu trong body |
| student-flag | Học viên có trong data.students của trang đang đọc và cờ = giá trị mong đợi; không thấy ⇒ "không thấy học viên ở trang 1" |
Error cases:
| Case | Expected Handling |
|---|---|
| Không gọi được Ops / LMS (mạng, timeout) | Hộp đỏ "Không gọi được Ops qua API: 400 13042 Failed to call SAPP Ops API: <lý do>" (BR-15) |
| HubSpot từ chối (tạo ticket) | Hộp đỏ với mã 12006 kèm câu từ chối của HubSpot (BR-15; chi tiết US-S09) |
| Luồng 4 thiếu URL LMS / thiếu token | Hộp đỏ mã 13058 / 13059 (chi tiết US-S08) |
| Tester bấm Huỷ ở hộp xác nhận GHI | Không gọi gì, bước giữ trạng thái cũ |
Acceptance Criteria của màn M2 ​
AC-M2.1 - Ô điền sẵn từ fixture ​
gherkin
Given fixture có class_id_l1 = "L1", user_a = "A"
When người dùng mở case 1.A
Then ô class_id của bước 1 hiện "L1" và ô transfer_from hiện "A"
When người dùng sửa ô transfer_from thành "X" rồi đổi user_a trong fixture thành "A2"
Then ô transfer_from vẫn là "X", ô class_id theo fixture mớiMaps to:
- Business Rule: BR-06, BR-09
AC-M2.2 - Khoá nút khi còn token chưa điền ​
gherkin
Given fixture chưa có class_id_l2
When người dùng mở bước "Bước 2 — Chuyển nhượng A → B tại L2"
Then nút "Chạy (GHI)" bị khoá
And hiện "Chưa điền: transfer_to_class_id. Điền ở fixture hoặc sửa trực tiếp ô trên."Maps to:
- Business Rule: BR-10
AC-M2.3 - Ô trống không được gửi, preview phản ánh đúng ​
gherkin
Given bước chuyển nhượng có ô transfer_to_class_id để trống
Then preview hiện "PUT /api/v1/classes/<L1>/students/transfer" và body JSON KHÔNG có transfer_to_class_id
When người dùng chạy bước
Then request tới Ops không chứa field đó và mind_keep không tự kiểm tra thiếu fieldMaps to:
- Business Rule: BR-11, BR-12, BR-16
AC-M2.4 - Xác nhận thao tác GHI ​
gherkin
Given bước có thao tác chuyển nhượng
Then nút ghi "Chạy (GHI)"
When người dùng bấm nút
Then hộp xác nhận hiện "Lệnh này GHI dữ liệu trên Ops dev và không có undo" kèm preview
When người dùng chọn Huỷ
Then không có request nào được gửiMaps to:
- Business Rule: BR-13
AC-M2.5 - Kết quả tự chấm hiển thị ​
gherkin
Given Ops trả HTTP 400 với error.code "400|100332"
When bước có 3 check (status 400, error-code, error-message) chạy xong
Then mỗi check hiện một dòng ✓ hoặc ✗ kèm giá trị thực
And hiện "3/3 đạt" màu xanh nếu đạt hết, màu đỏ nếu thiếu
And khối "Response Ops — HTTP 400" chứa body nguyên văn của OpsMaps to:
- Business Rule: BR-14, BR-17, BR-18
AC-M2.6 - Không gọi được hệ đích ​
gherkin
Given Ops dev không phản hồi
When người dùng chạy một bước gọi Ops
Then hộp đỏ hiện "Không gọi được Ops qua API: 400 13042 Failed to call SAPP Ops API: <lý do>"
And không có dòng check nào hiệnMaps to:
- Business Rule: BR-15
AC-M2.7 - Mẫu "Từ chối đúng" và "Cập nhật thành công" ​
gherkin
Scenario Outline: chấm theo mẫu
Given bước dùng mẫu <mẫu>
When Ops trả <response>
Then các check của mẫu đều đạt
Examples:
| mẫu | response |
| Từ chối đúng | HTTP 400, error.code = "400\|<mã>", error.message = "exceptions.<khoá>" |
| Cập nhật thành công | HTTP 200, success = true, data.message = "Update successfully" |Maps to:
- Business Rule: BR-18, BR-19
AC-M2.8 - Ghi ngược fixture ​
gherkin
Given bước "Tạo contact học viên" khai báo produces contact_id ← contact.id
Then bước hiện "Chạy xong sẽ ghi vào fixture: {contact_id} ← contact.id"
When bước chạy xong và response có contact.id = "123"
Then ô fixture contact_id = "123" và được lưu trình duyệt
And nếu contact.id rỗng / không có thì fixture không đổiMaps to:
- Business Rule: BR-20
Negative AC / Edge Cases của màn M2 ​
| ID | Tình huống | Hành vi mong đợi |
|---|---|---|
| EC-M2.1 | ⚠️ Observed — thao tác HubSpot / LMS | Hành vi hiện tại: hộp xác nhận vẫn ghi "GHI dữ liệu trên Ops …", hộp lỗi vẫn ghi "Không gọi được Ops qua API", khối response ghi "Response Ops" dù hệ đích là HubSpot / LMS (sapp-26297-step-call.tsx:437,551,576) — OQ-S-07 |
| EC-M2.2 | ⚠️ Observed — route GHI không cần đăng nhập | Hành vi hiện tại: mọi route 26297, kể cả GHI, là @Public() — ai gọi được API đều sửa được dữ liệu dev/staging SAPP (NFR-S-01, NFR-P-01) — OQ-S-06 |
| EC-M2.3 | Bấm Chạy liên tục | Nút khoá trong lúc "Đang chạy…"; bấm lại sau khi xong = gửi request mới (không có chống lặp phía hệ đích) |
| EC-M2.4 | Học viên nằm ngoài trang 1 (50 dòng) của danh sách lớp | Check cờ nút fail với "không thấy học viên ở trang 1" |
| EC-M2.5 | Body không phải mảng ở đường dẫn check danh sách | Check fail, giá trị thực "không phải mảng: …" |
| EC-M2.6 | ⚠️ Ghi PROD — thao tác GHI HubSpot khi chọn staging | Hành vi hiện tại: route staging/sapp-26297/hubspot/* dùng môi trường HubSpot "staging" = credentials portal prod (be/apps/api/src/sapp/sapp-26297.controller.ts:431,498,540 · be/libs/constants/src/lib/hubspot/hubspot.constants.ts:19-26); banner vẫn ghi "Không có route production" — OQ-S-19 |
Màn M3 - Bước SQL (sql) ​
| Field | Value |
|---|---|
| Figma frame | N/A |
| Trigger vào màn | Mở một case có bước loại sql |
| Mô tả ngắn | Tiêu đề bước + panel SQL của một prefix s26297.<prefix>: mỗi câu có tiêu đề, ghi chú, nội dung SQL, param (nếu có), nút chạy. Panel dùng chung với 26878 — cơ chế chi tiết ở US-P09 và US-D03 |
UI States ​
| State | Khi nào | Hiển thị | Figma frame |
|---|---|---|---|
| Default | Chưa chạy | Danh sách câu SQL của panel (tiêu đề, ghi chú, nội dung đã điền token) | N/A |
| Loading | Đang chạy câu | Theo US-P09 | N/A |
| Has data | Chạy xong | Bảng kết quả / số dòng ảnh hưởng (US-P09) | N/A |
| Empty | Câu đọc trả 0 dòng | Theo US-P09 | N/A |
| Error | Runner báo lỗi (thiếu param, SQL lỗi) | Theo US-P09 | N/A |
| Disabled | Theo US-P09 | — | N/A |
Fields ​
| Field | Required | Type | Default | Format | Validation | Error Message | Visibility | Depends On |
|---|---|---|---|---|---|---|---|---|
Param panel (chỉ target_type ở panel grpb và lms) | Theo câu | Text | Gợi ý giá trị | Chuỗi trạng thái lưu thật (BR-22) | Theo US-P09 | Theo US-P09 | Panel có param | — |
Token {key} trong SQL | — | — | Điền từ fixture; fixture thắng param trùng tên | — | BR-06 | — | — | Fixture |
Actions ​
A1 - Chạy câu SQL ​
| Item | Description |
|---|---|
| Trigger | Bấm chạy một câu trong panel |
| Behavior | Gửi id câu (không gửi SQL thô) tới SQL runner dùng chung trên DB Ops của môi trường đang chọn; câu GHI hỏi xác nhận (BR-21). Ghi vào: DB Ops dev/staging (với 19 câu GHI); không có key prod (NFR-S-02) |
| Success Result | Kết quả hiện dưới câu; câu có produces ghi ngược fixture (BR-20) |
Error cases:
| Case | Expected Handling |
|---|---|
| Thiếu param / lỗi SQL | Theo US-P09 |
Catalogue s26297.* — tổng quan (danh mục đầy đủ theo prefix ở US-S02 §4):
| Nhóm | Số câu đọc | Số câu GHI |
|---|---|---|
Tiền điều kiện, verify, integrity, tra cứu (pre, f1v, f2v, c31, c32, c35, c36.1, int, grpb.1, lms.1, hs5) | 20 | 0 |
Dựng điều kiện case, khôi phục, dọn (c12, c24, c25, c26, c33, c34, c36.2, grpb.2-3, clean, lms.2-3, hs5s) | 0 | 19 |
| Tổng | 20 | 19 |
Acceptance Criteria của màn M3 ​
AC-M3.1 - Chạy câu đọc theo id ​
gherkin
Given môi trường "dev" và fixture có class_id_l1, class_id_l2
When người dùng chạy câu "L1 và L2 cùng môn học" (s26297.pre.2) của case 0.1
Then runner chạy câu cùng id trên DB Ops dev và trả kết quả
And SQL gửi đi không phải SQL thô tester gõMaps to:
- Business Rule: BR-21, BR-22
AC-M3.2 - Câu GHI phải xác nhận ​
gherkin
Given câu s26297.c12.1 (A tại L1: completed = 3/10) là câu GHI
When người dùng bấm chạy
Then hộp xác nhận hiện trước khi chạy; huỷ thì không ghiMaps to:
- Business Rule: BR-21
Negative AC / Edge Cases của màn M3 ​
| ID | Tình huống | Hành vi mong đợi |
|---|---|---|
| EC-M3.1 | Fixture và param panel trùng tên | Giá trị fixture thắng (ô fixture rỗng không được đưa vào để không ghi đè — BR-06) |
| EC-M3.2 | Chạy câu GHI trên nhầm môi trường | Không có chặn thêm ngoài hộp xác nhận; dữ liệu Ops của môi trường đó bị sửa |
Màn M4 - Bước hướng dẫn (guide) ​
| Field | Value |
|---|---|
| Figma frame | N/A |
| Trigger vào màn | Mở một case có bước loại guide |
| Mô tả ngắn | Khung tiêu đề + danh sách dòng chữ (thao tác tay, lưu ý, tiền đề) |
UI States ​
| State | Khi nào | Hiển thị | Figma frame |
|---|---|---|---|
| Default | Mở case | Tiêu đề + danh sách gạch đầu dòng | N/A |
| Loading | N/A — không gọi gì | — | N/A |
| Has data | = Default | — | N/A |
| Empty | N/A — mọi bước guide có ≥ 1 dòng | — | N/A |
| Error | N/A | — | N/A |
| Disabled | N/A | — | N/A |
Fields ​
N/A — bước chỉ hiển thị chữ, không có ô nhập.
Actions ​
N/A — không có action; tester làm tay theo hướng dẫn.
Acceptance Criteria của màn M4 ​
AC-M4.1 - Chỉ hiển thị ​
gherkin
Given case 1.11 chỉ có một bước hướng dẫn "Chạy case 2.B ở luồng 2."
When người dùng mở case
Then hiện dòng chữ đó và không có request nào được gửiMaps to:
- Business Rule: BR-23
Negative AC / Edge Cases của màn M4 ​
| ID | Tình huống | Hành vi mong đợi |
|---|---|---|
| EC-M4.1 | Kết quả của bước làm tay | Tool không ghi nhận / không chấm — tester tự đánh giá |
5. Cross-feature AC ​
AC-X.1 - Màn bị khoá khi tool tắt ​
gherkin
Given Admin CMS tắt hoặc khoá tool "sapp.011"
When người dùng mở /works/sapp/tools/sapp-26297
Then màn bị ToolGate chặn theo US-P02Maps to:
- REL-01 (Affects - Khoá / trạng thái tool)
AC-X.2 - Bước SQL phụ thuộc runner dùng chung ​
gherkin
Given SQL runner dùng chung đang lỗi hoặc thiếu kết nối DB Ops của môi trường
When người dùng chạy một câu s26297.*
Then lỗi hiển thị theo US-P09; bước gọi API không bị ảnh hưởngMaps to:
- REL-04 (Affects - Ops SQL runner)
- REL-05 (Affects / Be Affected - panel SQL dùng chung với 26878)