- AC-001: Form có 3 fields: Full Name (required, 2-100 chars), Email (required, valid format), Password (required, min 8 chars, có uppercase + lowercase + number)
- AC-002: Khi submit thành công → app navigate tới OTP Verification screen
- AC-003: OTP gửi qua email trong vòng 60 giây, có hiệu lực 10 phút
- AC-004: Email đã tồn tại → hiện error "Email này đã được đăng ký. Đăng nhập?"
- AC-005: Password không đủ mạnh → hiện real-time inline error message
- AC-006: Register button disabled khi có validation error
- AC-007: Sau khi verify OTP thành công → tạo JWT + refresh token, navigate tới Home screen
QuickBite — Food Delivery Mobile App
Dự án thực hành đầy đủ từ A đến Z theo chuẩn quốc tế — Requirements, Test Plan, Test Cases (Frontend + Backend), Bug Reports, Performance, Security và Go/No-Go Report.
| Item | Detail |
|---|---|
| App Name | QuickBite v1.0.0 — Sprint 3 Build |
| Platforms | iOS 16+ (iPhone X và mới hơn), Android 11+ (API level 30+) |
| Frontend | React Native 0.73 + TypeScript |
| Backend | Node.js 20 + Express 4 + TypeScript |
| Database | PostgreSQL 15 (primary), Redis 7 (session + cache) |
| Cloud | AWS (ECS Fargate, RDS, ElastiCache, S3, CloudFront) |
| Auth | JWT (access 15min) + Refresh Token (7 days), OAuth2 (Google, Apple Sign-in) |
| Payment | Stripe (cards), GrabPay, MoMo (Vietnam) |
| Sprint Goal | User có thể đăng ký, đăng nhập, browse nhà hàng, thêm món vào giỏ và checkout thành công |
| Test Environment | staging.quickbite.app (cloned prod infra, seeded data) |
| CI/CD | GitHub Actions → AWS CodeDeploy |
2. API ↔ PostgreSQL: Transaction integrity, concurrent writes (race condition khi 2 users order món cuối cùng).
3. API ↔ Redis: Cache invalidation khi restaurant update menu.
4. API ↔ Stripe: Webhook handling khi payment success/fail.
5. App ↔ Firebase: Push notification sau khi order confirmed.
Docker Compose local. Mock payment. Reset daily. Dev only.
AWS clone. Stripe test mode. Seeded 50 restaurants. QA + Dev access.
Live users. Post-deploy smoke only. QA read-only access.
Sprint 3 scope: 5 Epics, 12 User Stories. QA review đánh dấu AC quality theo tiêu chí CCCTF (Completeness, Clarity, Consistency, Testability, Feasibility).
Epic 1: User Authentication
- AC-001: Sai password lần 1-4 → hiện error, counter giảm "N lần thử còn lại"
- AC-002: Sai password lần 5 → lock account 15 phút, hiện thông báo
- AC-003: Đăng nhập thành công → navigate Home, user data loaded
- AC-004: "Nhớ đăng nhập" (Remember Me) → refresh token lưu securely (Keychain iOS / Keystore Android)
- AC-005: Unverified account → hiện prompt "Chưa xác thực email. Gửi lại OTP?"
- AC-001: "Continue with Google" → Google OAuth flow → auto-create account nếu email mới
- AC-002: "Continue with Apple" → Apple Sign-in → yêu cầu trên iOS 13+ (Apple guideline)
- AC-003: Google/Apple email đã linked với password account → merge, không tạo duplicate
- AC-004: OAuth thất bại (user cancel, no network) → hiện error message, ở lại Login screen
Epic 2: Restaurant Browse
- AC-001: App request location permission. Nếu granted → dùng GPS. Nếu denied → prompt nhập địa chỉ tay
- AC-002: API trả về restaurants trong vòng 5km, sort by distance by default
- AC-003: Mỗi card hiển thị: tên, ảnh, rating (1 decimal), thời gian giao (phút), phí giao hàng, status (Mở/Đóng)
- AC-004: Restaurant "Đóng cửa" vẫn hiển thị nhưng có badge "Đóng" và không thể order
- AC-005: API response cache 5 phút (Redis). Pull-to-refresh force reload từ DB
- AC-006: Empty state khi không có nhà hàng trong 5km: "Chưa có nhà hàng tại khu vực của bạn"
- AC-007: Loading skeleton UI khi đang fetch data (không flash empty screen)
- AC-001: Search debounce 300ms — không call API mỗi keystroke
- AC-002: Tìm kiếm theo tên nhà hàng VÀ tên món ăn (full-text search PostgreSQL)
- AC-003: Min 2 ký tự để trigger search
- AC-004: "Không tìm thấy kết quả cho '...' " → suggest popular restaurants
- AC-005: Recent searches lưu local (max 10 items), có thể xóa từng item
Epic 3: Cart & Checkout
- AC-001: Cart chỉ chứa món từ 1 nhà hàng. Thêm món nhà hàng khác → alert "Xóa giỏ hàng cũ?"
- AC-002: Tăng/giảm số lượng (+ / −), min = 1. Số lượng 0 = xóa khỏi cart
- AC-003: Cart state persist khi đóng app (AsyncStorage + server-side cart)
- AC-004: Nếu món hết hàng sau khi thêm vào cart → hiện badge "Hết hàng" khi mở cart, không cho checkout
- AC-005: Subtotal tự động tính lại khi thay đổi quantity
- AC-006: Max 20 items per cart (business rule)
- AC-001: Checkout screen hiển thị: items list, subtotal, delivery fee, discount, total
- AC-002: User chọn địa chỉ giao hàng (saved addresses hoặc nhập mới)
- AC-003: Payment methods: Visa/MC/Amex card (via Stripe), GrabPay, MoMo, Cash on Delivery
- AC-004: "Place Order" button disabled khi: không có địa chỉ, cart rỗng, restaurant đóng cửa
- AC-005: Sau khi order thành công → Order Confirmation screen với order ID và estimated delivery time
- AC-006: Push notification "Đơn hàng #QB-XXXX đã được xác nhận" trong 30 giây
- AC-007: Nếu payment thất bại → order status "Payment Failed", user có thể retry
- AC-008: Idempotency — double-click "Place Order" không tạo 2 orders
US-004 AC-005: Cache 5 phút có thể conflict với AC-004 (restaurant đóng cửa). Nếu cache stale → user thấy nhà hàng mở nhưng thực ra đã đóng → checkout sẽ fail. Cần clarify behavior.
RTM mapping giữa User Story AC → Test Cases → Execution Status. Sprint 3 scope.
| Req ID | Acceptance Criteria | Test Case ID(s) | Type | Status |
|---|---|---|---|---|
| US-001 | AC-001: Form validation | TC-AUTH-001 đến 005 | Manual + Auto | Passed ✓ |
| AC-002: Navigate to OTP screen | TC-AUTH-006 | E2E Auto | Passed ✓ | |
| AC-003: OTP delivery 60s | TC-AUTH-007, 008 | Manual + API | Passed ✓ | |
| AC-004: Duplicate email error | TC-AUTH-009 | E2E Auto | Passed ✓ | |
| AC-005: Password strength real-time | TC-AUTH-010, 011 | E2E Auto | Failed ✗ BUG-001 | |
| AC-006: Button disabled state | TC-AUTH-012 | E2E Auto | Passed ✓ | |
| AC-007: JWT created after OTP verify | TC-AUTH-013, 014 | API Auto | Passed ✓ | |
| US-002 | AC-001: Failed attempt counter | TC-AUTH-015, 016 | Manual + API | Passed ✓ |
| AC-002: Account lock 15 min | TC-AUTH-017 | API Auto | Passed ✓ | |
| AC-004: Remember Me — secure storage | TC-AUTH-020 | Manual (device) | Passed ✓ | |
| US-003 | AC-001: Google OAuth flow | TC-AUTH-021, 022 | Manual | Passed ✓ |
| AC-003: No duplicate on merge | TC-AUTH-024 | API Auto | Failed ✗ BUG-002 | |
| US-004 | AC-002: Restaurants within 5km | TC-REST-001, 002 | API Auto | Passed ✓ |
| AC-003: Card data display | TC-REST-003 đến 007 | E2E Auto | Passed ✓ | |
| AC-005: Cache 5 min + pull refresh | TC-REST-010, 011 | API + Manual | Failed ✗ BUG-003 | |
| US-007 | AC-008: Idempotency (no double order) | TC-ORDER-015 | API Auto | Passed ✓ |
| AC-007: Payment failed → retry | TC-ORDER-012, 013 | Manual | Passed ✓ |
| Mục | Nội Dung |
|---|---|
| Test Objectives | Verify all Sprint 3 user stories meet acceptance criteria. Ensure no regressions in Auth module from Sprint 2. Validate API performance under 200 concurrent users. |
| In Scope | Registration, Login (email + OAuth), OTP verification, Restaurant listing + search, Cart management, Checkout + payment (Stripe test mode) |
| Out of Scope | Order tracking flow (Sprint 4), Admin dashboard, Driver app, Real payment transactions (staging uses Stripe test mode) |
| Test Types | Functional (manual + automated E2E), API testing, Performance (load), Security (OWASP scan), Mobile-specific (device matrix) |
| Test Environments | Primary: staging.quickbite.app | Devices: iOS 16 (iPhone 14), iOS 17 (iPhone 15 Pro), Android 13 (Pixel 7), Android 14 (Samsung S24) |
| Entry Criteria | Build deployed to staging ✓, Swagger API docs updated ✓, Dev unit tests passing (≥80% coverage) ✓, Test data seeded ✓ |
| Exit Criteria | ≥90% test cases executed, Pass rate ≥88%, 0 Critical open bugs, All P1 bugs fixed and verified, RTM 100% complete |
| Timeline | Day 1-2: Test design | Day 3-7: Manual + E2E execution | Day 8-9: Bug retest + regression | Day 10: Report + Go/No-Go |
| Risks | Staging Stripe webhook may have 30s delay (vs production 5s) → mock webhook tests. OAuth test account may need 2FA bypass. |
| Tools | Detox (E2E mobile automation), Postman + Newman (API), k6 (performance), OWASP ZAP (security), Jira (bug tracking), Allure (reporting) |
Test cases cho React Native app. Automated bằng Detox (E2E) + manual verification trên thiết bị thật.
Authentication — TC-AUTH
| TC ID | Test Case | Steps | Expected | Priority | Result |
|---|---|---|---|---|---|
| TC-AUTH-001 | Register form — happy path | Điền Name="John Doe", Email="john@test.com", Password="Test123!" → tap Register | Navigate tới OTP screen, hiện "OTP đã gửi tới john@test.com" | P1 | PASS |
| TC-AUTH-002 | Register — password quá ngắn | Nhập password "Test1!" (6 chars) → tap Register | Inline error "Mật khẩu tối thiểu 8 ký tự" xuất hiện dưới field | P1 | PASS |
| TC-AUTH-003 | Register — password không có số | Nhập password "TestPassword!" → focus out | Inline error "Mật khẩu phải có ít nhất 1 chữ số" | P1 | PASS |
| TC-AUTH-004 | Register — email format invalid | Nhập email "notanemail" → tap Register | Inline error "Email không hợp lệ", Register button disabled | P1 | PASS |
| TC-AUTH-005 | Register — email đã tồn tại | Đăng ký với email đã có trong DB | Error message "Email này đã được đăng ký. Đăng nhập?" với link tới Login | P1 | PASS |
| TC-AUTH-010 | Real-time password strength indicator | Gõ từng ký tự trong password field: "T" → "Te" → "Tes" → "Test" → "Test1" → "Test1!" → "Test1!A" | Strength indicator cập nhật sau mỗi keystroke: Weak → Weak → Weak → Medium → Medium → Strong → Strong | P2 | FAIL — BUG-001 |
| TC-AUTH-017 | Account lock sau 5 failed logins | Login sai password 5 lần liên tiếp | Sau lần 5: "Tài khoản tạm khóa 15 phút. Thử lại lúc 14:35" (hiện giờ cụ thể) | P1 | PASS |
| TC-AUTH-018 | Login — keyboard không đẩy UI lên quá mức (Android) | Trên Android, tap email field → keyboard hiện | Không bị keyboard che mất password field và Login button vẫn visible hoặc scrollable | P2 | PASS |
Restaurant Browse — TC-REST
| TC ID | Test Case | Steps | Expected | Priority | Result |
|---|---|---|---|---|---|
| TC-REST-001 | Home screen — restaurant list hiển thị đúng | Đăng nhập → grant location permission → xem Home screen | Danh sách nhà hàng trong 5km, sort by distance. Mỗi card có đủ: ảnh, tên, rating, thời gian, phí ship | P1 | PASS |
| TC-REST-004 | Restaurant "Đóng cửa" — không cho order | Tap vào nhà hàng có status "Đóng" | Badge "Đóng cửa" hiển thị. Tất cả menu item disabled. Không có "Add to Cart" button | P1 | PASS |
| TC-REST-010 | Pull-to-refresh bypass cache | 1. Gọi API xem restaurant. 2. Trong vòng 5 phút, Pull-to-refresh. 3. Check Network tab → HTTP call có được gọi không | Pull-to-refresh PHẢI trigger API call mới (bỏ qua Redis cache), server trả fresh data. Cache-Control header phải có no-cache khi force refresh. | P1 | FAIL — BUG-003 |
| TC-REST-012 | Search debounce — không spam API | Mở Network Monitor → Type "pho" nhanh, từng ký tự (< 300ms giữa mỗi ký tự) | Chỉ 1 API call được thực hiện sau khi ngừng gõ 300ms, không phải 3 calls cho "p", "ph", "pho" | P2 | PASS |
| TC-REST-013 | Empty state — no restaurants | Set location tới địa điểm không có nhà hàng (mock GPS) | Empty state illustration + text "Chưa có nhà hàng tại khu vực của bạn" + button "Thay đổi địa chỉ" | P3 | PASS |
Cart & Checkout — TC-CART / TC-ORDER
| TC ID | Test Case | Steps | Expected | Priority | Result |
|---|---|---|---|---|---|
| TC-CART-001 | Thêm món từ nhà hàng khác → alert | Thêm "Phở bò" từ Nhà Hàng A. Sau đó mở Nhà Hàng B → thêm "Cơm tấm" | Alert "Giỏ hàng của bạn đang có món từ Nhà Hàng A. Bắt đầu đơn mới từ Nhà Hàng B?" với [Hủy] [Đồng ý] | P1 | PASS |
| TC-CART-002 | Cart persist sau khi kill app | Thêm 2 món vào giỏ → force-kill app → mở lại app | Giỏ hàng vẫn còn đủ 2 món, số lượng đúng | P1 | PASS |
| TC-ORDER-001 | Checkout — complete happy path (Stripe) | Cart có 2 items → Checkout → chọn địa chỉ → chọn Visa → dùng Stripe test card 4242 4242 4242 4242 → Place Order | Order Confirmation screen với order #QB-XXXXX. Cart cleared. Push notification trong 30 giây. | P1 | PASS |
| TC-ORDER-012 | Payment failed → retry flow | Dùng Stripe test card 4000 0000 0000 0002 (declined) → Place Order | Error "Thẻ bị từ chối. Vui lòng thử lại hoặc chọn phương thức khác." Order không được tạo. User có thể chọn lại payment và retry. | P1 | PASS |
| TC-ORDER-015 | Idempotency — double tap Place Order | Tap "Place Order" 2 lần rất nhanh (< 300ms) | Chỉ 1 order được tạo trong DB. Idempotency key trên API prevent duplicate. Button bị disabled sau tap đầu. | P1 | PASS |
Base URL: https://staging.quickbite.app/api/v1. Auth header: Authorization: Bearer {token}. Test runner: Newman trong CI.
POST /auth/register
| TC ID | Scenario | Request Body | Expected Response | Result |
|---|---|---|---|---|
| API-AUTH-001 | Register thành công | {"name":"Test User","email":"new@test.com","password":"Test123!"} |
201 Created. Body: {"message":"OTP sent","userId":"uuid"}. OTP record tồn tại trong DB. |
PASS |
| API-AUTH-002 | Duplicate email | Email đã tồn tại trong DB | 409 Conflict. {"error":"EMAIL_EXISTS","message":"..."} |
PASS |
| API-AUTH-003 | Password quá yếu | {"password":"123456"} |
400 Bad Request. {"error":"VALIDATION_ERROR","fields":{"password":"..."}} |
PASS |
| API-AUTH-004 | Missing required field | Bỏ trống "name" | 400 Bad Request. Error message rõ ràng, không expose stack trace | PASS |
| API-AUTH-005 | SQL Injection trong email field | {"email":"test@test.com' OR '1'='1"} |
400 Bad Request (validation fail). KHÔNG trả về 200 hoặc DB error. | PASS |
POST /auth/login
| TC ID | Scenario | Input | Expected | Result |
|---|---|---|---|---|
| API-AUTH-010 | Login thành công | Valid credentials | 200 OK. Body: {"accessToken":"...","refreshToken":"...","expiresIn":900}. JWT valid, payload chứa userId và role. |
PASS |
| API-AUTH-011 | Sai password | Email đúng, password sai | 401 Unauthorized. {"error":"INVALID_CREDENTIALS"}. Response time KHÔNG phải 200ms chẵn (timing attack mitigation — phải có constant-time comparison). |
PASS |
| API-AUTH-012 | Account locked | Login sau khi bị lock | 429 Too Many Requests. Body có "retryAfter": 900 (seconds). |
PASS |
GET /restaurants
| TC ID | Scenario | Query Params | Expected | Result |
|---|---|---|---|---|
| API-REST-001 | Lấy restaurants trong radius | ?lat=10.762&lon=106.660&radius=5 |
200 OK. Array of restaurants, mỗi item có: id, name, imageUrl, rating, deliveryTime, deliveryFee, isOpen, distance (km). | PASS |
| API-REST-002 | No auth header | Gọi API không có Bearer token | 401 Unauthorized. {"error":"UNAUTHORIZED"} |
PASS |
| API-REST-003 | Expired JWT token | Dùng token đã quá 15 phút | 401. {"error":"TOKEN_EXPIRED"}. Client phải dùng refresh token để lấy token mới. |
PASS |
| API-REST-004 | Cache hit verification | Gọi cùng endpoint 2 lần trong 5 phút | Response header: X-Cache: HIT ở lần 2. Response time lần 2 < 50ms (vs lần 1 < 300ms). |
PASS |
POST /orders — Checkout API
{{base_url}}, {{auth_token}}, {{test_user_email}}. Chạy trong CI: newman run QuickBite.postman_collection.json -e staging.json --reporter-htmlextra.
Connect tới staging PostgreSQL. Verify data integrity sau các business operations.
UPDATE menu_items SET stock_quantity = stock_quantity - 1 WHERE id = ? AND stock_quantity > 0 RETURNING id trong 1 atomic statement. → Logged as BUG-004 (Critical).
| TC ID | Scenario | Device | Steps | Expected | Result |
|---|---|---|---|---|---|
| TC-MOB-001 | Interruption — phone call trong lúc checkout | Android 13 | Ở Checkout screen → trigger incoming call (simulated) → answer → hang up → return to app | App resume đúng Checkout screen, cart data còn nguyên, không tự-submit | PASS |
| TC-MOB-002 | Background → foreground sau 30 phút | iOS 17 | Mở app → background 30 phút → foreground | JWT có thể expire (15 min). App tự silent refresh token. Không bị logout đột ngột. Nếu refresh token cũng expire → navigate Login với message. | PASS |
| TC-MOB-003 | Mất mạng giữa chừng khi checkout | iPhone 14 + Android | Bắt đầu checkout → tắt WiFi khi đang submit order | App hiện "Mất kết nối. Vui lòng kiểm tra mạng." Không tạo partial order. Khi có mạng lại → user có thể retry. Không tạo duplicate order. | PASS |
| TC-MOB-004 | Xoay màn hình (orientation change) | iPad (nếu supported) | Ở Restaurant List → xoay landscape | Layout adjust properly, grid có thể chuyển từ 2 columns sang 3. Data không mất. | SKIP (không support iPad trong sprint này) |
| TC-MOB-005 | Android Back Button — Checkout screen | Android 13, 14 | Ở Checkout → nhấn hardware Back button | Alert "Bạn có muốn hủy đơn hàng không?" [Ở lại] [Hủy đơn]. Không tự navigate ra ngoài. | PASS |
| TC-MOB-006 | Deep link — share restaurant link | iOS + Android | Mở URL quickbite://restaurant/123 khi app đã install |
App mở và navigate thẳng đến Restaurant Detail screen của restaurant #123. Nếu chưa login → Login screen, sau login → navigate đúng restaurant. | PASS |
| TC-MOB-007 | Permission denied — Location | iOS 16 + Android 13 | Deny location permission → vào Home screen | Hiện prompt "Cho phép vị trí để xem nhà hàng gần bạn" hoặc input field nhập địa chỉ tay. Không crash. | PASS |
| TC-MOB-008 | App update — data migration | Android 13 | Install v0.9 → add items to cart → upgrade to v1.0 → open app | Cart data vẫn còn (backward compatible AsyncStorage schema). User không bị logout. | PASS |
P2 Devices (Simulator/Emulator OK): iPhone 12 Mini (iOS 16), OnePlus 11 (Android 13).
Excluded this sprint: Android < 11, iOS < 16, tablets.
Mục tiêu: Staging chịu tải 200 concurrent users (= 20% dự kiến peak production launch). SLA: P90 response < 2s, error rate < 1%.
Performance Test Results — Sprint 3
| Endpoint | P50 | P90 | P99 | Error Rate | TPS | Status |
|---|---|---|---|---|---|---|
| GET /restaurants | 180ms | 620ms | 1,200ms | 0.12% | 45 | ✓ SLA Met |
| GET /restaurants/:id/menu | 95ms | 310ms | 750ms | 0.05% | 62 | ✓ SLA Met |
| POST /auth/login | 210ms | 580ms | 1,100ms | 0.08% | 28 | ✓ SLA Met |
| POST /orders | 890ms | 2,750ms | 4,200ms | 0.8% | 12 | ✗ P90 FAIL (2750 > 2000ms) |
| GET /cart | 45ms | 120ms | 280ms | 0.01% | 110 | ✓ SLA Met |
SELECT * FROM menu_items WHERE id = ? trong loop cho mỗi cart item thay vì 1 query với IN (?). → BUG-005 (Major) filed. Fix: batch query menu_items với WHERE id IN (...) rồi map vào order items.
Security testing chạy trên staging. Tools: OWASP ZAP (automated scan) + manual testing. Tập trung vào các điểm rủi ro cao của fintech/food delivery app.
| TC ID | OWASP Category | Test Scenario | Method | Expected | Result |
|---|---|---|---|---|---|
| TC-SEC-001 | A01: Broken Access Control | User A xem order của User B bằng cách thay orderId trong URL | Postman: GET /orders/QB-000001 với token của User B | 403 Forbidden. Không trả về order data của User A cho User B. | PASS |
| TC-SEC-002 | A01: IDOR — Menu management | Normal user gọi DELETE /admin/restaurants/1 | Postman với user-level JWT | 403 Forbidden. Role-based access control hoạt động. | PASS |
| TC-SEC-003 | A02: Cryptographic Failures | Password stored dưới dạng gì trong DB? | SQL: SELECT password_hash FROM users WHERE email='test@test.com' | Giá trị bắt đầu bằng $2b$ (bcrypt, cost factor ≥ 10). KHÔNG phải MD5/SHA1/plaintext. |
PASS |
| TC-SEC-004 | A03: Injection | SQL injection trong search endpoint | GET /restaurants?search=pho' OR '1'='1 | Trả về kết quả bình thường (có filter) hoặc 400. KHÔNG trả về tất cả restaurants (injection failed). Dùng parameterized queries. | PASS |
| TC-SEC-005 | A03: NoSQL Injection | Inject trong JSON body | POST /auth/login body: {"email":{"$gt":""},"password":"x"} |
400 Bad Request hoặc 401. KHÔNG bypass authentication. | PASS |
| TC-SEC-006 | A07: Auth Failures | JWT algorithm confusion attack | Modify JWT header "alg":"none", remove signature |
401 Unauthorized. Server reject token với alg=none. | PASS |
| TC-SEC-007 | A07: Auth Failures | Brute force OTP | Script gửi 1000 OTP guesses liên tiếp | Rate limit sau 10 attempts. 429 Too Many Requests với Retry-After header. IP blocked sau excessive attempts. | FAIL — BUG-006 |
| TC-SEC-008 | A05: Security Misconfiguration | Server response headers | Xem response headers của bất kỳ API call | Có: Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options: DENY. KHÔNG có: X-Powered-By: Express, Server: nginx/x.y.z. |
PASS |
| TC-SEC-009 | A04: Insecure Design | Payment amount tampering | Postman: POST /orders với body tự set "totalAmount": 1 (thay vì giá thực) |
Server PHẢI tính lại total từ DB prices, không dùng client-submitted amount. Order total = giá thực từ database. | PASS |
| TC-SEC-010 | A09: Logging Failures | Sensitive data trong logs | Query CloudWatch logs / staging logs sau login và order operations | KHÔNG thấy: password, card number, CVV, full JWT token trong logs. OK để log: userId, email (truncated), request method, status code. | PASS |
| TC-SEC-011 | Mobile: Secure Storage | JWT stored ở đâu trong app? | Root Android device, kiểm tra SharedPreferences và file system | JWT và refresh token stored trong Android Keystore (không phải SharedPreferences plaintext) / iOS Keychain. Không thể extract mà không có biometrics. | PASS |
| TC-SEC-012 | Mobile: Certificate Pinning | MITM attack với proxy (Charles/Burp Suite) | Setup SSL proxy → chạy app | App từ chối kết nối khi certificate không match pinned cert. Network traffic không thể intercepted. | PASS |
Automation strategy: Detox cho E2E mobile, Newman cho API regression, k6 cho performance trong CI pipeline.
6 bugs tìm được. 2 Critical, 2 Major, 2 Minor. Format theo ISTQB standard.
2. Tap vào Password field
3. Gõ ký tự đầu tiên "T"
4. Quan sát strength indicator
2. Logout
3. Tap "Continue with Google" → dùng cùng abc@gmail.com account
4. Check DB
2. Admin đổi 1 nhà hàng từ "Mở" → "Đóng" trong dashboard
3. Trong vòng 5 phút: Pull-to-refresh trên Home screen
4. Kiểm tra nhà hàng đó có badge "Đóng" không?
Cache-Control: no-cache request header khi app gửi pull-to-refresh. API handler check header này → skip Redis, fetch từ DB → update cache.2. Dùng 2 Postman tabs: cả 2 send POST /orders với item trên, gửi đồng thời
3. Check DB:
SELECT stock_quantity FROM menu_items WHERE id = '...'SELECT stock_quantity WHERE id = ? → if > 0 → UPDATE SET stock_quantity - 1. Đây là TOCTOU (Time-of-Check-Time-of-Use) race condition. Giữa SELECT và UPDATE, 2 transactions cùng thấy stock = 1.UPDATE menu_items SET stock_quantity = stock_quantity - 1 WHERE id = ? AND stock_quantity > 0 RETURNING id. Nếu RETURNING trả về empty → throw "Out of stock" error. Toàn bộ trong 1 DB transaction.| KPI | Target | Actual | Status |
|---|---|---|---|
| Test Case Execution Rate | ≥90% | 90.5% (38/42) | ✓ Met |
| Pass Rate | ≥88% | 92.1% (35/38) | ✓ Met |
| Critical Bugs Open | 0 | 2 (BUG-004, BUG-006) | ✗ Not Met |
| Major Bugs Open | ≤1 | 2 (BUG-002, BUG-003) | ✗ Not Met |
| API Performance P90 | <2s cho tất cả endpoints | POST /orders P90 = 2,750ms | ✗ Not Met |
| Security Scan | 0 Critical/High vulns | 1 Critical (BUG-006 OTP) | ✗ Not Met |
| Defect Leakage (từ Sprint 2) | <5% | 0% (không tìm được bug cũ) | ✓ Met |
| Automation Coverage | ≥60% P1 test cases | 72% (18/25 P1 cases automated) | ✓ Met |
Bug Distribution
| Severity | Count | Fixed | Open | Defer |
|---|---|---|---|---|
| Critical | 2 | 0 | 2 | 0 |
| Major | 2 | 0 | 1 | 1 (Sprint 4) |
| Minor | 1 | 1 | 0 | 0 |
| Trivial | 1 | 0 | 0 | 1 |
| Total | 6 | 1 | 3 | 2 |
| Criteria | Requirement | Actual | Decision |
|---|---|---|---|
| Test Case Pass Rate | ≥88% | 92.1% | ✅ Go |
| Critical Bugs | 0 open | 2 open (BUG-004 race condition, BUG-006 security) | 🔴 No-Go |
| Security Vulnerabilities | 0 Critical/High | 1 Critical (OTP brute force) | 🔴 No-Go |
| API Performance | P90 <2s all endpoints | POST /orders P90 2,750ms | 🟡 Conditional |
| Requirement Coverage | 100% US tested | 90% (iPad scenarios skipped) | 🟡 Conditional |
| Regression | 0 regressions from Sprint 2 | 0 regressions found | ✅ Go |
| Google OAuth merge | No duplicate users | Duplicate users created (BUG-002) | 🔴 No-Go |
Recommendation: NO-GO
Blockers (phải fix trước khi release):
- BUG-004 — Race condition: stock_quantity có thể âm → overselling. Critical data integrity issue với financial impact trực tiếp.
- BUG-006 — OTP brute force: Security vulnerability cho phép account takeover. Với payment data trong app, đây là unacceptable risk.
- BUG-002 — Google OAuth merge: User mất order history. Data integrity + poor UX.
Có thể release với risk acceptance (PM sign-off required):
- BUG-005 — Performance /orders P90 2,750ms: Có thể defer nếu không phải peak season và monitor closely post-launch.
- BUG-003 — Pull-to-refresh cache: Workaround là đợi 5 phút. Defer to Sprint 4.
Estimated fix time: 2-3 ngày cho 3 blockers. Recommend mini-regression (1 ngày) sau khi fix. Revised release date: +4 days.
Signed: QA Engineer — Sprint 3 | Date: 2026-06-04
2. Security testing phải là mandatory, không phải optional: BUG-006 là critical security issue — nên chạy basic OWASP check trong CI, không chờ đến cuối sprint.
3. Performance regression test cần baseline: Chưa có Sprint 2 baseline nên khó biết P90 2,750ms là regression hay original state.
4. Race condition là blind spot: Cần thêm concurrent testing vào test checklist cho tất cả order/inventory operations.