Skip to content

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ế.

FieldValue
US IDUS-S01
Role thực hiệnR1 - Người dùng tool — Assumption (OQ về G-01 → OQ-S-01)
PhaseAs-built (đã triển khai @ e77c47a)
PriorityN/A — as-built
StatusDraft
NFR liên quanNFR-S-01, NFR-S-02, NFR-S-03, NFR-S-04, NFR-S-05 — xem Overview §5
OQ liên quanOQ-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àngAi cung cấpTrạng thái
D1Row tool sapp.011 trong bảng tools (migration seed) — thiếu ⇒ màn bị khoá (US-P02)BE / DevOps❓ OQ-S-11
D2Credentials Ops SAPP_OPS_* trong env_systems cho auto-login (US-P04, US-P08)Admin CMSFact: có dùng (be/libs/modules/src/lib/sapp/sapp-26297.common.service.ts:62-73)
D3Catalogue SQL s26297.* có đủ trên cả FE và BE (NFR-S-05); SQL runner hoạt động (US-P09)BE + FEFact: 39 entry khớp tại e77c47a

3. Business Rules ​

IDRuleÁp dụng ởĐã cover bởi (AC)Nguồn code (Mode B/C)
BR-01Mà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ànAC-M1.1be/libs/database/migrations/1786300000000-seed-tool-sapp-26297.ts:6-8 · fe/src/ui/pages/works/sapp/tools/shell.tsx:427-435
BR-02Chỉ 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ườngAC-M1.2fe/src/ui/components/works/sapp/sapp-26297-tool.tsx:179,222-234 · be/apps/api/src/sapp/sapp-26297.controller.ts:39-40
BR-03Luô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 productionM1 - bannerAC-M1.2sapp-26297-tool.tsx:238-244
BR-04Fixture đượ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ớ fixtureM1 - fixtureAC-M1.3, EC-M1.2sapp-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 - fixtureAC-M1.4sapp-26297-tool.tsx:195-201 · sapp-26297-flows.ts:36-191
BR-06Giá 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, M3AC-M2.1sapp-26297-tool.tsx:58-71
BR-07Mỗ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" để đóngM1 - caseAC-M1.5sapp-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ườngAC-M1.6sapp-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ạiM2 - fieldsAC-M2.1fe/src/ui/components/works/sapp/sapp-26297-step-call.tsx:417-427
BR-10Nú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ạyAC-M2.2sapp-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 / queryM2 - fieldsAC-M2.3sapp-26297-step-call.tsx:96-116 · sapp-26297-flows.ts:221-224
BR-12Preview 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 - previewAC-M2.3sapp-26297-step-call.tsx:191-204,528-530
BR-136 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ọiM2 - action Chạy (GHI)AC-M2.4sapp-26297-flows.ts:211-219 · sapp-26297-step-call.tsx:431-441,500-502
BR-14Vớ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ỗiM2 - kết quảAC-M2.5sapp-26297.controller.ts:31-41,65-76 · sapp-26297.common.service.ts:30-40,90-93
BR-15Chỉ 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ỗiAC-M2.6sapp-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-16Mind_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ửiM2 - gọi APIAC-M2.3be/libs/interface/src/lib/dto/sapp/sapp-26297.dto.ts:8-14 · sapp-26297.common.service.ts:96-103
BR-17Tự 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.5sapp-26297-step-call.tsx:461-483,555-583
BR-18Loại check và cách chấm theo bảng "Loại check" dưới M2M2 - kết quảAC-M2.5, AC-M2.7sapp-26297-step-call.tsx:251-395
BR-19Hai 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.7sapp-26297-flows.ts:387-403
BR-20Ghi 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, M3AC-M2.8sapp-26297-step-call.tsx:449-458,532-539 · sapp-26297-flows.ts:1550,1592,1636
BR-21Bướ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)M3AC-M3.1, AC-M3.2sapp-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-22So 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_TRANSFERREDM3AC-M3.1sapp-26297-flows.ts:10-11 · be/libs/constants/src/lib/sapp/sapp-26297-sql.constants.ts:13-14
BR-23Bướ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àoM4AC-M4.1sapp-26297-tool.tsx:109-117

4. Design &ZeroWidthSpace;

a. Mối liên hệ giữa các màn & User Flow &ZeroWidthSpace;

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 tay

b. Chi tiết từng màn &ZeroWidthSpace;

Màn M1 - Màn tool SAPP-26297 &ZeroWidthSpace;

FieldValue
Figma frameN/A
Trigger vào mànMở /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ắnTiê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 &ZeroWidthSpace;
StateKhi nàoHiển thịFigma frame
DefaultVừa vào mànMô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 đóngN/A
LoadingN/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 dataChọn một tabTóm tắt luồng + danh sách case của tab (mã · tên · "Kỳ vọng: …" · "Mở")N/A
EmptyN/A — mọi tab đều có case cố định (78 case)—N/A
ErrorId SQL mà case tham chiếu thiếu trong catalogue FEMàn lỗi khi tải (NFR-S-05)N/A
DisabledTool sapp.011 không có / bị khoáMàn bị ToolGate chặn (US-P02)N/A
Fields &ZeroWidthSpace;
FieldRequiredTypeDefaultFormatValidationError MessageVisibilityDepends On
Môi trườngYesChip chọn 1devdev | stagingBR-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ớiTextRỗng / giá trị đã lưuid OpsBR-06—Tab Chuẩn bị + 1, 2, 3A, 3B, 3C, 4 (BR-05)—
Nhóm 3B: class_id, user_x, user_yTheo bướcTextRỗngid OpsBR-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_tokenLuồng 4PasswordRỗng (không lưu)token học viênBR-04, NFR-S-03—Chuẩn bị + 4—
lms_user_id, lms_class_idLuồng 4 hồi quyTextRỗngidBR-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_2Luồng 5–6TextRỗngkhu_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_idLuồng 5–6TextRỗngidBR-20 (tự điền khi chạy bước)—Chuẩn bị + 5, 6Bướ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 &ZeroWidthSpace;
A1 - Chọn môi trường &ZeroWidthSpace;
ItemDescription
TriggerBấ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 ResultChip được chọn sáng; banner nêu môi trường mới

Error cases:

CaseExpected Handling
N/A — thao tác thuần giao diện—
A2 - Nhập / sửa fixture &ZeroWidthSpace;
ItemDescription
TriggerGõ vào một ô fixture
BehaviorCậ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 ResultMọi bước chưa sửa tay lấy giá trị mới (BR-09)

Error cases:

CaseExpected 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 &ZeroWidthSpace;
ItemDescription
TriggerBấ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
BehaviorBR-05, BR-07
Success ResultHiệ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:

CaseExpected Handling
N/A—
Acceptance Criteria của màn M1 &ZeroWidthSpace;
AC-M1.1 - Màn chỉ mở khi tool được bật &ZeroWidthSpace;
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-P02

Maps 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 &ZeroWidthSpace;
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 &ZeroWidthSpace;
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ống

Maps to:

  • Business Rule: BR-04
  • Field/Action: A2
AC-M1.4 - Ô fixture hiện theo tab &ZeroWidthSpace;
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 5

Maps to:

  • Business Rule: BR-05
AC-M1.5 - Mở case &ZeroWidthSpace;
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 &ZeroWidthSpace;
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 fixture

Maps to:

  • Business Rule: BR-08
Negative AC / Edge Cases của màn M1 &ZeroWidthSpace;
IDTình huốngHành vi mong đợi
EC-M1.1Mở 2 tab trình duyệt cùng lúcFixture ở 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.2Cử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ốngHà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.4Refresh giữa lúc bước đang chạyKế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) &ZeroWidthSpace;

FieldValue
Figma frameN/A
Trigger vào mànMở một case có bước loại call
Mô tả ngắnKhung 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 &ZeroWidthSpace;
StateKhi nàoHiển thịFigma frame
DefaultChư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ọiNút "Đang chạy…" bị khoáN/A
Has dataGọi xongDanh sách check ✓/✗ + giá trị thực; "x/y đạt"; khối thu gọn "Response Ops — HTTP n"N/A
EmptyBước không có check (vd pipeline, owner)Chỉ có response thô, không có tổng đạtN/A
ErrorRoute mind_keep trả lỗiHộp đỏ "Không gọi được Ops qua API: <status> <code> <message>"N/A
DisabledCòn ô chứa {token} chưa thayNút khoá, tooltip + dòng vàng "Chưa điền: …"N/A
Fields &ZeroWidthSpace;
FieldRequiredTypeDefaultFormatValidationError MessageVisibilityDepends On
Ô field của bước (tên field API, vd transfer_from, page_index, lop_moi)Theo caseText (token LMS: Password)Template của bước, điền từ fixtureTuỳ fieldBR-10 (còn {…} ⇒ khoá); BR-11 (trống ⇒ không gửi)"Chưa điền: <ô>…"AlwaysFixture (BR-09)
Actions &ZeroWidthSpace;
A1 - Chạy / Chạy (GHI) &ZeroWidthSpace;
ItemDescription
TriggerBấm "Chạy" (thao tác đọc) hoặc "Chạy (GHI)" (6 thao tác ghi)
BehaviorThao 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 ResultKết quả chấm hiện dưới bước

Danh mục 14 thao tác (route {env} = dev | staging):

Thao tácRoute mind_keepHệ đíchGHI?
Ứng viên nhận chuyển nhượngGET {env}/sapp-26297/classes/:classId/transfer-candidatesOpsKhông
Chuyển nhượngPUT {env}/sapp-26297/classes/:classId/students/transferOpsCó
Bản ghi chuyển nhượng hiệu lựcGET {env}/sapp-26297/classes/:classId/students/:studentId/transfer-recordOpsKhông
Hủy chuyển nhượngPUT {env}/sapp-26297/classes/:classId/students/:studentId/revert-transferOpsCó
Danh sách học viên lớpGET {env}/sapp-26297/classes/:classId/studentsOpsKhông
Xoá học viên khỏi lớpDELETE {env}/sapp-26297/classes/studentOps (xoá mềm)Có
Khoá học của tôi (LMS)GET {env}/sapp-26297/lms/courses + header X-Lms-TokenLMSKhông
Tạo contactPOST {env}/sapp-26297/hubspot/contactsHubSpotCó
Đọc contactGET {env}/sapp-26297/hubspot/contacts/:contactIdHubSpotKhông (không bước nào dùng — OQ-S-08)
Tạo ticketPOST {env}/sapp-26297/hubspot/ticketsHubSpotCó
Sửa ticket (kể cả kéo stage)PATCH {env}/sapp-26297/hubspot/tickets/:ticketIdHubSpotCó
Đọc ticketGET {env}/sapp-26297/hubspot/tickets/:ticketIdHubSpotKhông
Pipeline ticketGET {env}/sapp-26297/hubspot/ticket-pipelinesHubSpotKhông
OwnerGET {env}/sapp-26297/hubspot/ownersHubSpotKhô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
statusMã HTTP của hệ đích = giá trị mong đợi
error-code / error-messageerror.code / error.message trong body = giá trị mong đợi
replacements-keysMọi khoá liệt kê trong error.replacements có giá trị khác rỗng / null
data-nulldata = null
path-equals / path-presentGiá 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-classMả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-eligibleNgườ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-flagHọ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:

CaseExpected 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 tokenHộp đỏ mã 13058 / 13059 (chi tiết US-S08)
Tester bấm Huỷ ở hộp xác nhận GHIKhông gọi gì, bước giữ trạng thái cũ
Acceptance Criteria của màn M2 &ZeroWidthSpace;
AC-M2.1 - Ô điền sẵn từ fixture &ZeroWidthSpace;
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ới

Maps to:

  • Business Rule: BR-06, BR-09
AC-M2.2 - Khoá nút khi còn token chưa điền &ZeroWidthSpace;
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 &ZeroWidthSpace;
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 field

Maps to:

  • Business Rule: BR-11, BR-12, BR-16
AC-M2.4 - Xác nhận thao tác GHI &ZeroWidthSpace;
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ửi

Maps to:

  • Business Rule: BR-13
AC-M2.5 - Kết quả tự chấm hiển thị &ZeroWidthSpace;
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 Ops

Maps to:

  • Business Rule: BR-14, BR-17, BR-18
AC-M2.6 - Không gọi được hệ đích &ZeroWidthSpace;
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ện

Maps to:

  • Business Rule: BR-15
AC-M2.7 - Mẫu "Từ chối đúng" và "Cập nhật thành công" &ZeroWidthSpace;
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 &ZeroWidthSpace;
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 đổi

Maps to:

  • Business Rule: BR-20
Negative AC / Edge Cases của màn M2 &ZeroWidthSpace;
IDTình huốngHành vi mong đợi
EC-M2.1⚠️ Observed — thao tác HubSpot / LMSHà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ậpHà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.3Bấm Chạy liên tụcNú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.4Học viên nằm ngoài trang 1 (50 dòng) của danh sách lớpCheck cờ nút fail với "không thấy học viên ở trang 1"
EC-M2.5Body không phải mảng ở đường dẫn check danh sáchCheck fail, giá trị thực "không phải mảng: …"
EC-M2.6⚠️ Ghi PROD — thao tác GHI HubSpot khi chọn stagingHà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) &ZeroWidthSpace;

FieldValue
Figma frameN/A
Trigger vào mànMở một case có bước loại sql
Mô tả ngắnTiê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 &ZeroWidthSpace;
StateKhi nàoHiển thịFigma frame
DefaultChưa chạyDanh 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âuTheo US-P09N/A
Has dataChạy xongBảng kết quả / số dòng ảnh hưởng (US-P09)N/A
EmptyCâu đọc trả 0 dòngTheo US-P09N/A
ErrorRunner báo lỗi (thiếu param, SQL lỗi)Theo US-P09N/A
DisabledTheo US-P09—N/A
Fields &ZeroWidthSpace;
FieldRequiredTypeDefaultFormatValidationError MessageVisibilityDepends On
Param panel (chỉ target_type ở panel grpb và lms)Theo câuTextGợi ý giá trịChuỗi trạng thái lưu thật (BR-22)Theo US-P09Theo US-P09Panel có param—
Token {key} trong SQL——Điền từ fixture; fixture thắng param trùng tên—BR-06——Fixture
Actions &ZeroWidthSpace;
A1 - Chạy câu SQL &ZeroWidthSpace;
ItemDescription
TriggerBấm chạy một câu trong panel
BehaviorGử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 ResultKết quả hiện dưới câu; câu có produces ghi ngược fixture (BR-20)

Error cases:

CaseExpected Handling
Thiếu param / lỗi SQLTheo US-P09

Catalogue s26297.* — tổng quan (danh mục đầy đủ theo prefix ở US-S02 §4):

NhómSố câu đọcSố 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)200
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)019
Tổng2019
Acceptance Criteria của màn M3 &ZeroWidthSpace;
AC-M3.1 - Chạy câu đọc theo id &ZeroWidthSpace;
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 &ZeroWidthSpace;
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 ghi

Maps to:

  • Business Rule: BR-21
Negative AC / Edge Cases của màn M3 &ZeroWidthSpace;
IDTình huốngHành vi mong đợi
EC-M3.1Fixture và param panel trùng tênGiá trị fixture thắng (ô fixture rỗng không được đưa vào để không ghi đè — BR-06)
EC-M3.2Chạy câu GHI trên nhầm môi trườngKhô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) &ZeroWidthSpace;

FieldValue
Figma frameN/A
Trigger vào mànMở một case có bước loại guide
Mô tả ngắnKhung tiêu đề + danh sách dòng chữ (thao tác tay, lưu ý, tiền đề)
UI States &ZeroWidthSpace;
StateKhi nàoHiển thịFigma frame
DefaultMở caseTiêu đề + danh sách gạch đầu dòngN/A
LoadingN/A — không gọi gì—N/A
Has data= Default—N/A
EmptyN/A — mọi bước guide có ≥ 1 dòng—N/A
ErrorN/A—N/A
DisabledN/A—N/A
Fields &ZeroWidthSpace;

N/A — bước chỉ hiển thị chữ, không có ô nhập.

Actions &ZeroWidthSpace;

N/A — không có action; tester làm tay theo hướng dẫn.

Acceptance Criteria của màn M4 &ZeroWidthSpace;
AC-M4.1 - Chỉ hiển thị &ZeroWidthSpace;
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ửi

Maps to:

  • Business Rule: BR-23
Negative AC / Edge Cases của màn M4 &ZeroWidthSpace;
IDTình huốngHành vi mong đợi
EC-M4.1Kết quả của bước làm tayTool không ghi nhận / không chấm — tester tự đánh giá

5. Cross-feature AC &ZeroWidthSpace;

AC-X.1 - Màn bị khoá khi tool tắt &ZeroWidthSpace;

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-P02

Maps to:

  • REL-01 (Affects - Khoá / trạng thái tool)

AC-X.2 - Bước SQL phụ thuộc runner dùng chung &ZeroWidthSpace;

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ưởng

Maps to:

  • REL-04 (Affects - Ops SQL runner)
  • REL-05 (Affects / Be Affected - panel SQL dùng chung với 26878)