CYBERSOFT
Đăng nhập

Thực chiến SaaS: billing theo usage, multi-tenancy & RBAC

Thực chiến doanh nghiệpDịch vụ SaaSPlaywrightSecurityThực tếPhỏng vấn
🗓 1 tháng trước21 phút đọc·👁 1,156 lượt xem👤 425 người đọc

Kiểm thử nền tảng SaaS đa tenant theo hướng oracle-first: cô lập dữ liệu chống rò rỉ chéo, hoá đơn theo usage và proration đúng đến từng xu, ma trận RBAC thực thi ở API, dunning; dùng storageState theo vai trò/tenant, seed API và test phủ định uỷ quyền.

1. Bối cảnh nghiệp vụ: SaaS đa tenant tính tiền theo mức dùng

Một nền tảng SaaS B2B phục vụ ba nghìn tổ chức khách hàng (tenant) trên cùng một hạ tầng dùng chung. Mỗi tenant tự quản lý người dùng, phân quyền và bị tính tiền theo mức sử dụng thực tế: số lời gọi API, dung lượng lưu trữ, số ghế người dùng hoạt động. Ba trụ cột chất lượng sống còn là cô lập dữ liệu giữa các tenant, tính hoá đơn theo usage phải đúng đến từng xu, và phân quyền RBAC phải được thực thi ở tầng API chứ không chỉ ẩn nút. Sai một trong ba đều là sự cố nghiêm trọng.

SLA nghiệp vụ đặt ra ba cam kết cứng. Thứ nhất, không bao giờ có rò rỉ dữ liệu chéo giữa tenant — đây là cam kết bảo mật tuyệt đối, một lần rò là mất khách và có thể vi phạm quy định bảo vệ dữ liệu. Thứ hai, hoá đơn phải khớp chính xác với usage đã ghi nhận, kể cả khi khách nâng hoặc hạ gói giữa kỳ (proration). Thứ ba, quy trình đòi nợ (dunning) khi thanh toán thất bại phải chạy đúng lịch và không khoá nhầm tài khoản đang trả tiền đầy đủ.

CÔ LẬP TENANT / TENANT ISOLATION Tenant A (Acme) usage · invoice · users RBAC: admin/member tenant_id = A Tenant B (Globex) usage · invoice · users RBAC: admin/member tenant_id = B Không rò rỉ chéo
Hai tenant chia sẻ hạ tầng nhưng dữ liệu tuyệt đối không rò rỉ chéo.

Chiến lược kiểm thử của bài này dựa trên ba kỹ thuật gắn với ba trụ cột. Với RBAC và đa tenant, ta dùng storageState cho từng cặp vai trò và tenant để đăng nhập sẵn, kết hợp kiểm thử phủ định uỷ quyền — cố tình truy cập tài nguyên của tenant khác và khẳng định bị chặn. Với billing, ta seed usage qua API rồi khẳng định hoá đơn tính lại bằng oracle độc lập khớp đến từng xu. Toàn bộ tuân theo nguyên tắc oracle-first: không khẳng định giao diện đẹp, mà khẳng định bất biến bảo mật và tiền.

⚠️ Ẩn nút trên giao diện KHÔNG phải là phân quyền. Nếu API không tự kiểm quyền, kẻ tấn công gọi thẳng endpoint là vượt qua. Mọi ca RBAC phải kiểm ở tầng API.

2. Kiến trúc và mô hình dữ liệu đa tenant

Hệ thống dùng mô hình shared-database, shared-schema: mọi tenant chung một cơ sở dữ liệu, mỗi bản ghi mang một cột tenant_id. Mô hình này tiết kiệm chi phí nhưng đặt gánh nặng cô lập lên tầng ứng dụng: mọi truy vấn phải lọc theo tenant_id của phiên hiện tại, thường thông qua một lớp middleware gán ngữ cảnh tenant. Đây chính là điểm rủi ro số một: chỉ cần một truy vấn quên mệnh đề lọc tenant là dữ liệu rò rỉ chéo. Vì vậy oracle cô lập phải được kiểm ở mọi endpoint đọc dữ liệu.

typescript
// prisma/schema.prisma (trích) — mọi bảng có tenantId, index kép để lọc nhanh.
model Tenant {
  id        String   @id @default(cuid())
  name      String
  plan      String   // FREE | PRO | ENTERPRISE
  users     User[]
  usages    Usage[]
  invoices  Invoice[]
}

model User {
  id        String  @id @default(cuid())
  email     String
  role      String  // ADMIN | MEMBER | BILLING
  tenantId  String
  tenant    Tenant  @relation(fields: [tenantId], references: [id])
  @@unique([tenantId, email])
}

model Usage {
  id        String   @id @default(cuid())
  tenantId  String
  metric    String   // api_calls | storage_gb | seats
  quantity  Int
  at        DateTime
  @@index([tenantId, metric, at])   // luôn lọc theo tenant TRƯỚC
}

model Invoice {
  id        String   @id @default(cuid())
  tenantId  String
  periodEnd DateTime
  amount    Int      // đơn vị: cent, tránh số thực
  status    String   // OPEN | PAID | PAST_DUE | VOID
}

Từ mô hình này ta rút ra bất biến cô lập cốt lõi: với mọi endpoint và mọi cặp tenant khác nhau, dữ liệu trả về không bao giờ chứa bản ghi thuộc tenant khác. Bất biến billing: tổng tiền hoá đơn của một tenant bằng tổng usage đã ghi nhận nhân đơn giá theo gói, cộng phần proration nếu đổi gói. Và bất biến RBAC: một hành động chỉ thành công khi vai trò của người gọi có quyền tương ứng trong ma trận, kiểm ngay ở API. Ba bất biến này là oracle xuyên suốt bài.

Lưu tiền bằng đơn vị nguyên (cent) thay vì số thực để tránh sai số dấu phẩy động khi cộng dồn hàng nghìn dòng usage.

3. storageState theo vai trò và tenant

Đăng nhập lại trong từng test vừa chậm vừa dễ giòn. Playwright giải quyết bằng storageState: ta đăng nhập một lần trong bước setup, lưu cookie và token vào tệp JSON, rồi các test nạp sẵn trạng thái đó. Với hệ thống nhiều vai trò và nhiều tenant, ta tạo một ma trận storageState: admin của tenant A, member của tenant A, admin của tenant B, và người dùng billing. Nhờ đó mỗi test chỉ việc chọn đúng danh tính cần thiết mà không phải trả giá đăng nhập lặp lại.

typescript
// tests/auth.setup.ts — tạo storageState cho từng (vai trò, tenant).
import { test as setup } from '@playwright/test';

const identities = [
  { key: 'adminA',   email: 'admin@acme.test',   tenant: 'A', role: 'ADMIN' },
  { key: 'memberA',  email: 'member@acme.test',  tenant: 'A', role: 'MEMBER' },
  { key: 'adminB',   email: 'admin@globex.test', tenant: 'B', role: 'ADMIN' },
  { key: 'billingA', email: 'billing@acme.test', tenant: 'A', role: 'BILLING' },
];

for (const id of identities) {
  setup(`đăng nhập ${id.key}`, async ({ page }) => {
    await page.goto('/login');
    await page.getByLabel('Email').fill(id.email);
    await page.getByLabel('Mật khẩu').fill(process.env.TEST_PASSWORD!);
    await page.getByRole('button', { name: 'Đăng nhập' }).click();
    await page.waitForURL('/dashboard');
    await page.context().storageState({ path: `.auth/${id.key}.json` });
  });
}
🔒

Nội dung bị khoá

Bạn đang xem bản xem trước. Đăng nhập để mở khoá toàn bộ bài viết và tất cả tài liệu Tester/QA.

Đã xem 24% nội dung
  • Đọc trọn vẹn mọi bài viết
  • Lưu bài & ghi chú cá nhân
  • Theo dõi tiến độ đã đọc
  • Luyện phỏng vấn, ISTQB, Mock
Đăng nhập để đọc tiếp

Chưa có mã? Lấy mã qua Fanpage / Zalo CyberSoft.

0

💬 Bình luận (0)

Bạn có thể đọc mọi bình luận. Đăng nhập để bình luận

Chưa có bình luận nào. Hãy là người đầu tiên!