Hướng dẫn test — GIW POC Identity Platform

Bản: 21/09/2026 · Môi trường: local, localhost only

Ba phần, làm theo thứ tự: A. Khởi động → B. Test bằng tay trên giao diện (15 phút) → C. Test tự động (2 phút)


A. Khởi động

A1. Bật Docker

colima start

Bỏ qua nếu docker ps đã chạy được.

A2. Lên stack

cd "/Users/alexnguyen/data/Project/Galaxy Holding/Galaxy_ID/poc/identity"
docker compose up -d

Lần đầu sau khi sửa code sẽ lâu hơn (--build). Bình thường khoảng 30 giây.

A3. Kiểm tra 6 service

docker compose ps

Phải thấy đủ 6 dòng, postgres / keycloak / mock-hr-api / entitlement / session đều healthy:

giw-postgres          Up (healthy)
giw-keycloak          Up (healthy)
giw-mock-hr-api       Up (healthy)
giw-entitlement       Up (healthy)
giw-session           Up (healthy)
giw-wifi-portal       Up

Nếu giw-keycloak chưa healthy, đợi thêm 30 giây — nó khởi động chậm nhất.

A4. Mở portal

open http://localhost:3000

B. Test bằng tay — 8 kịch bản

Mỗi kịch bản: làm gì → phải thấy gì → điều đáng chú ý.

Mẹo: mở DevTools (Cmd+Option+I) → tab Network trước khi bắt đầu. Xuyên suốt cả 8 bước, thanh địa chỉ không bao giờ rời localhost:3000 và mọi request đều về localhost:3000. Đó là điểm chính của kiến trúc API.


B1. Màn đầu

Làm: mở http://localhost:3000

Phải thấy:

  • Header VietjetAir.com │ SkyFi, nút ngôn ngữ góc phải
  • Dòng chuyến bay VJ178 · SGN→HAN · Wi-Fi sẵn sàng
  • Ba nút: Đăng nhập Galaxy ID (đỏ đặc) · Đăng ký Galaxy ID (viền đỏ) · Đăng nhập CBNV (viền xám)
  • Khung vàng: "Đây là bước xác thực danh tính..."

Chú ý: chưa có gì mở mạng. Xác thực và cấp quyền là hai việc khác nhau.


B2. Đăng ký tài khoản mới

Làm: bấm Đăng ký Galaxy ID, điền:

Ô Giá trị
Họ và tên Nguyễn Văn Test
Email test01@example.test (đổi số mỗi lần chạy lại)
Mật khẩu Passw0rd!23

Bấm Tạo tài khoản và kết nối.

Phải thấy: nhảy thẳng sang màn Kết nối thành công — vòng tròn xanh, dấu tích.

Trường Giá trị
Loại người dùng CUSTOMER
Gói quyền lợi WIFI_CUSTOMER_BASIC
Hạng / QoS BASIC · STANDARD
Số thiết bị 1
Nguồn quyền lợi GALAXY_ID_LOGIN
Trạng thái phiên mạng GRANTED

Chú ý: một lần bấm chạy 4 việc — tạo tài khoản qua Admin API, đăng nhập, xin entitlement, mở session mạng. Hành khách chỉ có một câu hỏi: "tôi online chưa?"


B3. Đăng xuất và đăng nhập lại

Làm: kéo xuống cuối, bấm Đăng xuất. Rồi Đăng nhập Galaxy ID với chính email vừa tạo.

Phải thấy: về lại màn Kết nối thành công, vẫn BASIC.

Chú ý: đăng xuất thu hồi luôn phiên mạng chứ không chỉ xoá cookie. Kiểm chứng ở B8.

Tài khoản có sẵn nếu không muốn tự tạo:

Email Mật khẩu
an.nguyen@example.test Passw0rd!23
binh.tran@example.test Passw0rd!23
chi.le@example.test Passw0rd!23

B4. Đăng nhập CBNV — thành công

Làm: Đăng xuất → Đăng nhập CBNV:

Ô Giá trị
Mã nhân viên VJ12345
Số CCCD 001234567890

Phải thấy:

Trường Giá trị
Loại người dùng EMPLOYEE
Gói quyền lợi WIFI_EMPLOYEE_PACKAGE
Hạng / QoS BOOST · HIGH
Số thiết bị 2
Nguồn quyền lợi EMPLOYEE_VERIFICATION · EMP-12345

Kéo xuống khối Token claims. Đây là chỗ đáng soi nhất:

"user_type": "EMPLOYEE",
"employee_verified": true,
"employee_ref": "EMP-12345",
"company": "VIETJET"

Không có citizenId ở bất kỳ đâu. Bấm Cmd+F tìm 0012345 trên trang — không ra gì.

Chú ý: form do portal render, nhưng Keycloak mới là bên gọi HR. Portal không có credential HR, không tự verify được ai.

Nhân viên khác để thử:

Mã CCCD Công ty
GLX67890 001234500001 GALAXY
SVC24680 001234500002 SOVICO

B5. Bốn kiểu từ chối

Thử lần lượt, quay lại màn CBNV mỗi lần:

# Mã / CCCD Phải thấy Vì sao
1 VJ00000 / 001234567890 "Thông tin đăng nhập không đúng" Không có nhân viên này
2 VJ12345 / 000000000000 Y hệt câu trên Sai CCCD. Giống hệt để không ai dò được mã nào có thật
3 VJ99999 / 001234500003 "Hồ sơ nhân viên không ở trạng thái đang làm việc. Vui lòng liên hệ phòng nhân sự." Đã nghỉ việc. Nói rõ được vì người này đã nhập đúng CCCD
4 VJ55555 / 001234500004 "Hồ sơ này không thuộc diện dùng Wi-Fi trên tàu bay" Đang làm việc nhưng không thuộc diện

Chú ý case 1 và 2: thông báo phải giống hệt nhau từng chữ. Khác nhau là lộ ra mã nhân viên nào có thật. Chú ý case 3: chỉ đến được sau khi CCCD đúng, nên nói thẳng lý do không lộ gì, mà lại đỡ cho người ta thử đi thử lại vô ích.


B6. ⭐ Kịch bản đáng xem nhất

Làm: Đăng nhập CBNV với PTX10001 / 001234500005

Phải thấy: "Xác minh thành công, nhưng đơn vị của bạn chưa thuộc diện dùng Wi-Fi trên tàu bay."

Chú ý: HR xác nhận người này là nhân viên thật, đang làm việc, đủ điều kiện. Xác thực thành công. Vẫn không được vào mạng — vì Entitlement Service là một quyết định riêng.

Đây là bằng chứng kiến trúc không phải con dấu cao su: bạn là ai và bạn được gì do hai hệ thống khác nhau trả lời.


B7. Bắt hệ thống hỏng

Làm: mở Terminal, cho HR treo:

curl -X POST localhost:3001/admin/fault \
  -H 'Content-Type: application/json' \
  -d '{"mode":"timeout","delayMs":4000}'

Rồi thử lại B4 (VJ12345 / 001234567890).

Phải thấy: "Hệ thống nhân sự phản hồi chậm. Vui lòng thử lại." (đợi ~7 giây)

Kiểm tra quan trọng: thử Đăng nhập Galaxy ID với an.nguyen@example.test — vẫn vào bình thường. HR sập chỉ ảnh hưởng luồng CBNV.

Thử tiếp kiểu sập hẳn:

curl -X POST localhost:3001/admin/fault \
  -H 'Content-Type: application/json' -d '{"mode":"unavailable"}'

→ "Hệ thống nhân sự tạm thời không truy cập được."

Trả lại bình thường:

curl -X POST localhost:3001/admin/fault \
  -H 'Content-Type: application/json' -d '{"mode":"none"}'

Chú ý: không nhánh nào cấp quyền tạm, không có đường vòng. Hỏng thì đóng.


B8. Truy vết một hành trình

Làm: đăng nhập CBNV thành công lần nữa, copy giá trị ở dòng Correlation (dạng corr-xxxx...), rồi:

CORR=corr-<dán-vào-đây>
for c in giw-wifi-portal giw-keycloak giw-mock-hr-api giw-entitlement giw-session; do
  echo "── $c"; docker logs $c 2>&1 | grep "$CORR"
done

Phải thấy: cùng một mã chạy suốt 5 service — portal nhận, Keycloak xác thực, HR verify, Entitlement quyết định, Session mở mạng.

Chú ý: mã này in ngay trên màn hình cho hành khách. Gọi CSKH đọc 1 mã là tra được toàn bộ hành trình. Và nó không sinh ra từ CCCD hay dữ liệu cá nhân nào.


B9. Kiểm tra trình duyệt không cầm gì

Làm: DevTools → Application → Cookies → http://localhost:3000

Phải thấy: đúng một cookie tên sid, giá trị là UUID vô nghĩa, cờ HttpOnly ✓ và SameSite=Strict.

DevTools → Network, lọc theo Fetch/XHR, đăng nhập lại: mọi request đều về localhost:3000. Không có request nào tới localhost:8080.

Chú ý: trình duyệt không giữ token, không biết địa chỉ Keycloak, HR, Entitlement hay Session. Đó là ranh giới BFF.


C. Test tự động

C1. Bộ E2E — 59 test

cd "/Users/alexnguyen/data/Project/Galaxy Holding/Galaxy_ID/poc/identity"
node tests/e2e.mjs

Khoảng 30 giây. Kết quả mong đợi:

59 test · 59 pass · 0 fail

Chia theo nhóm:

Nhóm Số test Kiểm gì
1 · Service health 2 6 service sống, portal ở chế độ api
1b · Mode B 5 Đường browser OIDC (production candidate) chưa mục
2 · Customer 11 Đăng nhập, refresh, logout, chống dò tài khoản
3 · Registration 4 Đăng ký, trùng email, validate
4 · Employee 8 Xác minh CBNV, claims, không có CCCD
5 · Failure cases 13 13 nhánh hỏng, không nhánh nào cấp quyền
6 · API contract 10 Idempotency, enumeration, guardrail 422
7 · CCCD audit 3 Quét log, user record, event log

C2. Bộ Bruno — 35 request

cd api/collection

npx @usebruno/cli run "00 Portal API" --env local

npx @usebruno/cli run "01 HR Verification" --env local \
  --env-var hrApiKey=giw-poc-hr-key-change-me

npx @usebruno/cli run "03 Session" --env local \
  --env-var sessionApiKey=giw-poc-session-key-change-me

Riêng nhóm Entitlement cần token thật:

cd "/Users/alexnguyen/data/Project/Galaxy Holding/Galaxy_ID/poc/identity"
TOKEN=$(node tests/token.mjs employee | tail -1)
cd api/collection
npx @usebruno/cli run "02 Entitlement" --env local --env-var accessToken="$TOKEN"

Tổng mong đợi: 35 request · 83 assertion · tất cả pass.

Muốn bấm tay thì cài app Bruno, mở thư mục api/collection, chọn environment local.

C3. Kiểm tra OpenAPI

cd api
npx @redocly/cli lint giw-poc

Mong đợi: Woohoo! Your API description is valid. 🎉 — 0 warning.

C4. Audit dữ liệu nhạy cảm

cd "/Users/alexnguyen/data/Project/Galaxy Holding/Galaxy_ID/poc/identity"
for c in giw-keycloak giw-mock-hr-api giw-entitlement giw-session giw-wifi-portal; do
  printf "%-20s " "$c"
  docker logs $c 2>&1 | grep -cE "001234567890|Passw0rd" | sed 's/^0$/sạch/'
done

Mọi dòng phải là sạch. Bất kỳ số nào khác 0 là lỗi nghiêm trọng.


D. Xử lý khi hỏng

Triệu chứng Làm gì
dependency failed to start: giw-keycloak is unhealthy Đợi thêm 30 giây. Còn hỏng thì docker logs giw-keycloak | tail -30
Portal trả IDP_UNAVAILABLE docker compose ps xem Keycloak còn sống không
Đăng nhập báo sai mật khẩu dù gõ đúng Sai quá nhiều lần → brute-force khoá 60 giây. Đợi rồi thử lại
Test fail sau khi sửa code Dựng lại: docker compose up -d --build
Muốn sạch hoàn toàn docker compose down -v && docker compose up -d --build — xoá DB, import lại realm, tạo lại user test
Cổng bị chiếm lsof -ti tcp:8080

Bảng triệu chứng đầy đủ: RUNBOOK.md §6.


E. Dữ liệu test

Toàn bộ là dữ liệu tổng hợp. Không có người thật, hồ sơ nhân viên thật hay CCCD thật. Tên miền .test theo RFC 6761, vĩnh viễn không resolve được.

Khách hàng — mật khẩu Passw0rd!23

an.nguyen@example.test · binh.tran@example.test · chi.le@example.test

Nhân viên

Mã CCCD Công ty Trạng thái Kết quả
VJ12345 001234567890 VIETJET ACTIVE ✅ BOOST
GLX67890 001234500001 GALAXY ACTIVE ✅ BOOST
SVC24680 001234500002 SOVICO ACTIVE ✅ BOOST
VJ99999 001234500003 VIETJET INACTIVE ❌ nghỉ việc
VJ55555 001234500004 VIETJET ACTIVE ❌ không thuộc diện
PTX10001 001234500005 PARTNER-X ACTIVE ❌ Entitlement chặn
VJ00000 bất kỳ — — ❌ không tồn tại

F. Tắt

docker compose down        # giữ database
docker compose down -v     # xoá sạch, lần sau import lại realm
colima stop                # tắt cả Docker VM

GIW POC Identity Platform · bản demo local · không phải production · sinh từ repository