@sungen/driver-data-factory 3.2.10-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ # Field-type: currency (money) — from common-checklist.xlsx (Input box · Currency).
2
+ type: currency
3
+ description: Số tiền — số nguyên/thập phân theo đơn vị tiền tệ; lưu ý leading-zero & giá trị 0.
4
+ constraints:
5
+ min: 0
6
+ max: 999999999 # placeholder
7
+ decimalPlaces: 0 # placeholder (JPY=0, USD=2 …)
8
+ valid:
9
+ - { id: CHK-CUR-01, value: "1000", note: "số tiền hợp lệ" }
10
+ - { id: CHK-CUR-02, value: "0", note: "giá trị 0 phải hiểu là 0, KHÔNG phải 'chưa nhập'" }
11
+ boundary:
12
+ - { id: CHK-CUR-B1, gen: "Min / Min+1" }
13
+ - { id: CHK-CUR-B2, gen: "Max / Max-1" }
14
+ invalid:
15
+ - { id: CHK-CUR-I1, gen: "chữ cái / ký tự lạ", reason: "không phải số", errorRef: M13 }
16
+ - { id: CHK-CUR-I2, gen: "Min-1 / Max+1", reason: "vượt biên", errorRef: M13 }
17
+ - { id: CHK-CUR-I3, value: "", reason: "rỗng (tuỳ spec)", errorRef: M02 }
18
+ notes:
19
+ - "Kiểm tra riêng: leading zero (0100) có bị cắt mất không; format hiển thị dấu phân cách nghìn."
20
+ uses_security: [sqli]
@@ -0,0 +1,30 @@
1
+ # Field-type: email — seeded from common-checklist.xlsx (Input box · Email) + phuong-phap §Nguồn-2.
2
+ # errorRef (Mxx) are PLACEHOLDERS — the generator maps them onto the project's real message codes;
3
+ # if the project spec has none, the value is marked as a chua-chot (open) point, not invented.
4
+ type: email
5
+ description: Email half-width, format <local-part>@<domain>
6
+ format: "<local-part>@<domain>"
7
+ constraints:
8
+ charset: half-width
9
+ maxLocal: 64
10
+ maxTotal: 255
11
+ uses_security: [sqli, xss]
12
+ valid:
13
+ - { id: CHK-EMAIL-01, value: "user@example.com", note: "normal half-width" }
14
+ - { id: CHK-EMAIL-02, value: "u.name+tag@sub.example.co.jp", note: "dotted local + subdomain" }
15
+ boundary:
16
+ - { id: CHK-EMAIL-B1, gen: "local@ đúng maxLocal", note: "local part = max (64)" }
17
+ - { id: CHK-EMAIL-B2, gen: "local@ maxLocal-1", note: "local part = max-1" }
18
+ - { id: CHK-EMAIL-B3, gen: "chuỗi tổng = maxTotal", note: "total length = max (255)" }
19
+ invalid:
20
+ - { id: CHK-EMAIL-I1, value: "user@", reason: "thiếu domain", errorRef: M13 }
21
+ - { id: CHK-EMAIL-I2, value: "user@.com", reason: "dấu . sai vị trí", errorRef: M13 }
22
+ - { id: CHK-EMAIL-I3, value: "u@@b.com", reason: "hai dấu @", errorRef: M13 }
23
+ - { id: CHK-EMAIL-I4, value: "@example.com", reason: "thiếu local-part", errorRef: M13 }
24
+ - { id: CHK-EMAIL-I5, value: "user@example.com", reason: "full-width character", errorRef: M39 }
25
+ - { id: CHK-EMAIL-I6, gen: "local > 64 ký tự", reason: "vượt maxLocal", errorRef: M13 }
26
+ - { id: CHK-EMAIL-I7, gen: "tổng > 255 ký tự", reason: "vượt maxTotal", errorRef: M13 }
27
+ - { id: CHK-EMAIL-I8, value: "", reason: "rỗng (required)", errorRef: M02 }
28
+ notes:
29
+ - "Chuẩn hoá: lowercase, NFKC, trim trước khi so khớp."
30
+ - "Soft-delete: nếu hệ thống gắn hậu tố vào email đã xoá mềm → test maxTotal THẤP hơn giới hạn cột DB để chừa chỗ hậu tố."
@@ -0,0 +1,20 @@
1
+ # Field-type: Katakana name (JP-specific) — from phuong-phap §Nguồn-2 (kinh nghiệm đặc thù JP).
2
+ type: katakana
3
+ description: Trường chỉ nhận Katakana (họ/tên kana trong hệ thống Nhật).
4
+ constraints:
5
+ charset: "full-width Katakana only"
6
+ valid:
7
+ - { id: CHK-KANA-01, value: "ヤマダ", note: "Katakana chuẩn" }
8
+ - { id: CHK-KANA-02, value: "ジョウジ", note: "có dakuten + trường âm hợp lệ" }
9
+ boundary:
10
+ - { id: CHK-KANA-B1, gen: "đúng min / max độ dài" }
11
+ invalid:
12
+ - { id: CHK-KANA-I1, value: "yamada", reason: "lẫn ký tự Latin", errorRef: M39 }
13
+ - { id: CHK-KANA-I2, value: "やまだ", reason: "lẫn Hiragana", errorRef: M39 }
14
+ - { id: CHK-KANA-I3, value: "山田", reason: "lẫn Kanji", errorRef: M39 }
15
+ - { id: CHK-KANA-I4, value: "ヤマダ", reason: "half-width kana (nếu spec chỉ nhận full-width)", errorRef: M39 }
16
+ - { id: CHK-KANA-I5, value: "ゝ々", reason: "ký tự lặp — có tiền lệ lỗi thật nhiều hệ thống", errorRef: M39 }
17
+ - { id: CHK-KANA-I6, value: "ャュョ", reason: "kana nhỏ đứng một mình / sai vị trí", errorRef: M39 }
18
+ notes:
19
+ - "Chú ý: kana nhỏ (ゃゅょ), trường âm (ー), dakuten/handakuten, half↔full-width kana."
20
+ uses_security: [sqli, xss]
@@ -0,0 +1,20 @@
1
+ # Field-type: number (float) — from common-checklist.xlsx (Input box · Number_Float).
2
+ type: number-float
3
+ description: Số thập phân — chữ số + dấu thập phân; số chữ số phần lẻ theo spec.
4
+ constraints:
5
+ min: 0.0 # placeholder
6
+ max: 999999.99 # placeholder
7
+ decimalPlaces: 2 # placeholder
8
+ valid:
9
+ - { id: CHK-FLOAT-01, value: "123.45", note: "hợp lệ" }
10
+ - { id: CHK-FLOAT-02, value: "0.5", note: "phần nguyên = 0" }
11
+ boundary:
12
+ - { id: CHK-FLOAT-B1, gen: "Min / Min+1" }
13
+ - { id: CHK-FLOAT-B2, gen: "Max / Max-1" }
14
+ - { id: CHK-FLOAT-B3, gen: "đúng số chữ số thập phân / thừa 1 chữ số" }
15
+ invalid:
16
+ - { id: CHK-FLOAT-I1, gen: "chữ cái", reason: "không phải số", errorRef: M13 }
17
+ - { id: CHK-FLOAT-I2, gen: "format sai (nhiều dấu chấm, +/- sai vị trí)", reason: "format sai", errorRef: M13 }
18
+ - { id: CHK-FLOAT-I3, gen: "Min-1 / Max+1", reason: "vượt biên", errorRef: M13 }
19
+ - { id: CHK-FLOAT-I4, value: "", reason: "rỗng/multi-space (tuỳ spec)", errorRef: M02 }
20
+ uses_security: [sqli]
@@ -0,0 +1,21 @@
1
+ # Field-type: number (integer) — from common-checklist.xlsx (Input box · Number_Integer).
2
+ type: number-integer
3
+ description: Số nguyên — chỉ chữ số, có thể có +/- ở vị trí hợp lệ.
4
+ constraints:
5
+ min: 0 # placeholder theo spec
6
+ max: 2147483647 # placeholder
7
+ valid:
8
+ - { id: CHK-INT-01, value: "123", note: "chỉ số" }
9
+ - { id: CHK-INT-02, value: "-45", note: "có dấu - đầu (nếu spec cho phép âm)" }
10
+ boundary:
11
+ - { id: CHK-INT-B1, gen: "Min / Min+1" }
12
+ - { id: CHK-INT-B2, gen: "Max / Max-1" }
13
+ invalid:
14
+ - { id: CHK-INT-I1, gen: "chữ cái", reason: "không phải số", errorRef: M13 }
15
+ - { id: CHK-INT-I2, gen: "số full-width (Japanese number)", reason: "full-width", errorRef: M39 }
16
+ - { id: CHK-INT-I3, gen: "+/- sai vị trí (giữa chuỗi)", reason: "format sai", errorRef: M13 }
17
+ - { id: CHK-INT-I4, gen: "Min-1 / Max+1", reason: "vượt biên", errorRef: M13 }
18
+ - { id: CHK-INT-I5, value: "", reason: "rỗng/multi-space (tuỳ spec)", errorRef: M02 }
19
+ notes:
20
+ - "leading zero (007) có bị cắt không; redundant spaces trước/sau phải xử lý đúng."
21
+ uses_security: [sqli]
@@ -0,0 +1,30 @@
1
+ # Field-type: password — seeded from common-checklist.xlsx (Input box · Password) + phuong-phap example.
2
+ type: password
3
+ description: Password — độ dài + thành phần ký tự theo spec; kèm show/hide + mask.
4
+ format: "theo ràng buộc spec (min/max, ≥1 hoa/thường/số tuỳ dự án)"
5
+ constraints:
6
+ minLength: 7 # placeholder — ghi đè bằng min thật của spec dự án
7
+ maxLength: 20 # placeholder
8
+ requires: [uppercase, lowercase, digit] # placeholder theo spec
9
+ uses_security: [sqli, xss]
10
+ valid:
11
+ - { id: CHK-PW-01, value: "Passw0rd", note: "đủ hoa+thường+số, trong khoảng" }
12
+ - { id: CHK-PW-02, note: "alpha only / numeric only / mix — nếu spec cho phép" }
13
+ - { id: CHK-PW-03, note: "ký tự đặc biệt hợp lệ theo spec (vd ._@)" }
14
+ boundary:
15
+ - { id: CHK-PW-B1, gen: "đúng minLength", note: "min (vd Aa1bcde = 7)" }
16
+ - { id: CHK-PW-B2, gen: "minLength+1", note: "min+1" }
17
+ - { id: CHK-PW-B3, gen: "đúng maxLength", note: "max" }
18
+ - { id: CHK-PW-B4, gen: "maxLength-1", note: "max-1" }
19
+ invalid:
20
+ - { id: CHK-PW-I1, value: "", reason: "rỗng/multi-space (required)", errorRef: M02 }
21
+ - { id: CHK-PW-I2, gen: "< minLength", reason: "dưới độ dài tối thiểu", errorRef: M29 }
22
+ - { id: CHK-PW-I3, gen: "> maxLength", reason: "vượt độ dài tối đa", errorRef: M30 }
23
+ - { id: CHK-PW-I4, value: "passw0rd", reason: "thiếu chữ hoa", errorRef: M31 }
24
+ - { id: CHK-PW-I5, value: "PASSW0RD", reason: "thiếu chữ thường", errorRef: M32 }
25
+ - { id: CHK-PW-I6, value: "Password", reason: "thiếu chữ số", errorRef: M33 }
26
+ - { id: CHK-PW-I7, gen: "full-width Japanese", reason: "ký tự full-width", errorRef: M39 }
27
+ - { id: CHK-PW-I8, gen: "space before/after hoặc giữa", reason: "khoảng trắng thừa", errorRef: M13 }
28
+ notes:
29
+ - "Kiểm tra riêng: icon show/hide password (hiển thị + toggle mask↔unmask), ký tự mask đúng spec."
30
+ - "Confirm Password: kế thừa toàn bộ case của password + case (confirm == password) và (confirm != password → lỗi)."
@@ -0,0 +1,23 @@
1
+ # Field-type: phone number — from common-checklist.xlsx (Input box · Phone number).
2
+ type: phone
3
+ description: Số điện thoại — chỉ số, cho phép () - + ở vị trí hợp lệ.
4
+ constraints:
5
+ charset: "digits + () - +"
6
+ minLength: 8 # placeholder theo spec
7
+ maxLength: 15 # placeholder
8
+ valid:
9
+ - { id: CHK-PHONE-01, value: "0987654321", note: "chỉ số" }
10
+ - { id: CHK-PHONE-02, value: "+84-90-1234567", note: "có + và - đúng vị trí" }
11
+ - { id: CHK-PHONE-03, value: "(028)38221234", note: "có () đúng vị trí" }
12
+ boundary:
13
+ - { id: CHK-PHONE-B1, gen: "đúng min / min+1" }
14
+ - { id: CHK-PHONE-B2, gen: "đúng max / max-1" }
15
+ invalid:
16
+ - { id: CHK-PHONE-I1, gen: "ký tự cấm []:,;\"<>\\?", reason: "ký tự không hợp lệ", errorRef: M13 }
17
+ - { id: CHK-PHONE-I2, gen: "số Nhật (full-width)", reason: "ký tự full-width", errorRef: M39 }
18
+ - { id: CHK-PHONE-I3, gen: "chữ cái", reason: "không phải số", errorRef: M13 }
19
+ - { id: CHK-PHONE-I4, value: "090 123 4567", reason: "khoảng trắng giữa số", errorRef: M13 }
20
+ - { id: CHK-PHONE-I5, gen: "+/- không ở vị trí đầu; () sai vị trí", reason: "format sai", errorRef: M13 }
21
+ - { id: CHK-PHONE-I6, gen: "min-1 / max+1", reason: "vượt biên", errorRef: M13 }
22
+ - { id: CHK-PHONE-I7, value: "", reason: "rỗng/multi-space (tuỳ spec)", errorRef: M02 }
23
+ uses_security: [sqli]
@@ -0,0 +1,16 @@
1
+ # Field-type: postcode / zipcode — from common-checklist.xlsx (Input box · Postcode/Zipcode).
2
+ type: postcode
3
+ description: Mã bưu chính — format theo vùng (JP 7 số, có thể có dấu -).
4
+ constraints:
5
+ format: "region-specific (vd JP: NNN-NNNN)"
6
+ length: 7 # placeholder
7
+ valid:
8
+ - { id: CHK-ZIP-01, value: "1500001", note: "7 số (JP)" }
9
+ - { id: CHK-ZIP-02, value: "150-0001", note: "có dấu - nếu spec cho phép" }
10
+ boundary:
11
+ - { id: CHK-ZIP-B1, gen: "đúng độ dài / độ dài-1 / +1" }
12
+ invalid:
13
+ - { id: CHK-ZIP-I1, gen: "chữ cái / ký tự lạ", reason: "sai ký tự", errorRef: M13 }
14
+ - { id: CHK-ZIP-I2, gen: "sai format vùng", reason: "sai format", errorRef: M13 }
15
+ - { id: CHK-ZIP-I3, value: "", reason: "rỗng (tuỳ spec)", errorRef: M02 }
16
+ uses_security: [sqli]
@@ -0,0 +1,20 @@
1
+ # Field-type: string (text 1 dòng) — from common-checklist.xlsx (Input box · String).
2
+ type: string
3
+ description: Chuỗi 1 dòng — chấp nhận alnum, JP half/full-width, special, unicode, space.
4
+ constraints:
5
+ minLength: 1 # placeholder theo spec
6
+ maxLength: 255 # placeholder
7
+ uses_security: [sqli, xss]
8
+ valid:
9
+ - { id: CHK-STR-01, value: "Normal text 123", note: "alnum + space" }
10
+ - { id: CHK-STR-02, gen: "half-width Japanese", note: "半角カナ" }
11
+ - { id: CHK-STR-03, gen: "full-width Japanese", note: "全角かな漢字" }
12
+ - { id: CHK-STR-04, gen: "special chars ~!@#$%^&*()_+{}:|<>?,./;'[]-=`", note: "ký tự đặc biệt hợp lệ" }
13
+ - { id: CHK-STR-05, gen: "unicode / emoji nếu spec cho phép" }
14
+ - { id: CHK-STR-06, gen: "copy-paste full-width JP", note: "phải xử lý đúng khi paste" }
15
+ boundary:
16
+ - { id: CHK-STR-B1, gen: "đúng min / min+1" }
17
+ - { id: CHK-STR-B2, gen: "đúng max / max-1" }
18
+ invalid:
19
+ - { id: CHK-STR-I1, value: "", reason: "rỗng/multi-space (success/fail tuỳ spec)", errorRef: M02 }
20
+ - { id: CHK-STR-I2, gen: "min-1 / max+1", reason: "vượt biên", errorRef: M13 }
@@ -0,0 +1,9 @@
1
+ # Security payload bank: Broken authentication — from common-checklist.xlsx (Broken authentication).
2
+ class: broken-auth
3
+ description: Xác thực yếu — session/token/brute-force.
4
+ payloads:
5
+ - { id: CHK-SEC-AUTH-01, technique: "brute force login không giới hạn", expect: "có lockout/captcha/rate-limit" }
6
+ - { id: CHK-SEC-AUTH-02, technique: "session không hết hạn sau logout / đổi mật khẩu", expect: "invalidate session cũ" }
7
+ - { id: CHK-SEC-AUTH-03, technique: "session fixation — dùng lại session id trước đăng nhập", expect: "cấp session mới sau login" }
8
+ - { id: CHK-SEC-AUTH-04, technique: "token đoán được / không hết hạn", expect: "token ngẫu nhiên mạnh, có TTL" }
9
+ oracle: "Đăng nhập/duy trì phiên trái phép = lỗ hổng."
@@ -0,0 +1,8 @@
1
+ # Security payload bank: IDOR — from common-checklist.xlsx (Insecure Direct Object Reference).
2
+ class: idor
3
+ description: Insecure Direct Object Reference — đổi id để truy cập tài nguyên của người khác.
4
+ payloads:
5
+ - { id: CHK-SEC-IDOR-01, technique: "đổi id trên URL/param sang record của user khác", expect: "403/404, KHÔNG lộ dữ liệu người khác" }
6
+ - { id: CHK-SEC-IDOR-02, technique: "đoán id tuần tự (1,2,3…) trên API detail", expect: "kiểm tra quyền sở hữu, từ chối" }
7
+ - { id: CHK-SEC-IDOR-03, technique: "thao tác (edit/delete) resource ngoài quyền qua id", expect: "chặn ở server, không chỉ ẩn UI" }
8
+ oracle: "Truy cập/sửa được dữ liệu ngoài quyền = lỗ hổng. Kỳ vọng: authorization check theo ownership phía server."
@@ -0,0 +1,8 @@
1
+ # Security payload bank: Input validation — from common-checklist.xlsx (Input validation).
2
+ class: input-validation
3
+ description: Ràng buộc đầu vào phía server (không chỉ FE).
4
+ payloads:
5
+ - { id: CHK-SEC-IV-01, technique: "vượt boundary khi FE bị bypass (gọi API trực tiếp)", expect: "server cũng chặn" }
6
+ - { id: CHK-SEC-IV-02, technique: "sai kiểu dữ liệu (string vào field số)", expect: "reject với lỗi hợp lệ" }
7
+ - { id: CHK-SEC-IV-03, technique: "control/null bytes, ký tự điều khiển", expect: "loại bỏ/reject" }
8
+ oracle: "FE qua được nhưng BE phải chặn — test trực tiếp API bỏ qua FE."
@@ -0,0 +1,11 @@
1
+ # Security payload bank: SQL Injection — from common-checklist.xlsx (SQL Injection sheet).
2
+ # Reusable adversarial values injected into the `invalid` group of any field via uses_security: [sqli].
3
+ class: sqli
4
+ description: SQL Injection — dữ liệu đầu vào nhằm phá câu truy vấn / bypass xác thực.
5
+ payloads:
6
+ - { id: CHK-SEC-SQLI-01, value: "admin' --", target: "login username", expect: "KHÔNG được đăng nhập bỏ qua mật khẩu" }
7
+ - { id: CHK-SEC-SQLI-02, value: "test' OR '1'='1", target: "ô tìm kiếm", expect: "KHÔNG trả về toàn bộ dữ liệu" }
8
+ - { id: CHK-SEC-SQLI-03, value: "test@test.com' OR '1'='1", target: "email đăng ký", expect: "xử lý hợp lệ, không lưu sai dữ liệu" }
9
+ - { id: CHK-SEC-SQLI-04, value: "test', isAdmin = 1 --", target: "sửa hồ sơ", expect: "KHÔNG nâng quyền trái phép" }
10
+ - { id: CHK-SEC-SQLI-05, value: "2023-01-01' OR '1'='1", target: "bộ lọc report", expect: "chỉ trả dữ liệu đúng filter" }
11
+ oracle: "Bất kỳ dấu hiệu bypass/trả sai dữ liệu = lỗ hổng. Kỳ vọng: escape/parametrize an toàn, báo lỗi hợp lệ."
@@ -0,0 +1,10 @@
1
+ # Security payload bank: Upload file — from common-checklist.xlsx (Upload file function).
2
+ class: upload
3
+ description: Upload file độc / vượt ràng buộc.
4
+ payloads:
5
+ - { id: CHK-SEC-UP-01, technique: "file trá hình extension (shell.php.jpg)", expect: "kiểm tra MIME thật + whitelist, từ chối" }
6
+ - { id: CHK-SEC-UP-02, technique: "MIME mismatch (đổi Content-Type)", expect: "verify nội dung thật" }
7
+ - { id: CHK-SEC-UP-03, technique: "oversize vượt giới hạn", expect: "báo lỗi kích thước" }
8
+ - { id: CHK-SEC-UP-04, technique: "path traversal trong tên file (../../)", expect: "sanitize tên file, lưu đúng thư mục" }
9
+ - { id: CHK-SEC-UP-05, technique: "tên file chứa <script> / metadata độc", expect: "xử lý an toàn, không render ra DOM" }
10
+ oracle: "Thực thi/ghi ngoài phạm vi = lỗ hổng."
@@ -0,0 +1,12 @@
1
+ # Security payload bank: XSS Injection — from common-checklist.xlsx (XSS Injection sheet).
2
+ class: xss
3
+ description: Cross-Site Scripting — chèn script qua input/URL/attribute/file để thực thi trái phép.
4
+ payloads:
5
+ - { id: CHK-SEC-XSS-01, value: "<script>alert('XSS')</script>", kind: "Reflected/Stored", expect: "hiển thị text thuần, KHÔNG thực thi script" }
6
+ - { id: CHK-SEC-XSS-02, value: "<img src=x onerror=alert(1)>", kind: "HTML attribute", expect: "không thực thi onerror" }
7
+ - { id: CHK-SEC-XSS-03, value: "<svg onload=alert(1)>", kind: "SVG payload", expect: "không thực thi onload / loại bỏ thuộc tính" }
8
+ - { id: CHK-SEC-XSS-04, value: "\"><script>alert(1)</script>", kind: "Attribute injection", expect: "không phá vỡ HTML, escape an toàn" }
9
+ - { id: CHK-SEC-XSS-05, value: "<a href=\"javascript:alert(1)\">click me</a>", kind: "Dangerous link", expect: "href bị vô hiệu / sanitize" }
10
+ - { id: CHK-SEC-XSS-06, value: "<script>alert(1)</script>.jpg", kind: "File name / metadata", expect: "tên file xử lý an toàn, không render ra DOM" }
11
+ - { id: CHK-SEC-XSS-07, value: "<h1>test</h1>", kind: "HTML injection", expect: "hiển thị dạng text, không render thẻ" }
12
+ oracle: "Alert hiển thị / thẻ được render = lỗ hổng. Kỳ vọng: escape/sanitize, không innerHTML/eval user input."
@@ -0,0 +1,6 @@
1
+ # Technique: Boundary Value Analysis — from phuong-phap §Nguồn-3.
2
+ technique: boundary
3
+ description: Khi field có min/max rõ ràng — sinh giá trị tại/quanh biên.
4
+ rule: "Với [min,max]: sinh Min-1 (invalid), Min, Min+1, Max-1, Max, Max+1 (invalid)."
5
+ applies_to: [number-integer, number-float, currency, string, password, phone, postcode]
6
+ note: "Phải có 'đúng biên' (Min, Max), không chỉ 'quá biên' — off-by-one thường lọt ở đây."
@@ -0,0 +1,6 @@
1
+ # Technique: Decision Table — from phuong-phap §Nguồn-3.
2
+ technique: decision-table
3
+ description: Nhiều điều kiện kết hợp cùng ảnh hưởng 1 kết quả.
4
+ rule: "Liệt kê tổ hợp điều kiện → hành động; sinh @cases theo mỗi rule row."
5
+ applies_to: [login, permission, business-flow]
6
+ example: "Login: mật khẩu đúng/sai × account khoá/không × 2FA bật/tắt."
@@ -0,0 +1,6 @@
1
+ # Technique: Equivalence Partitioning — from phuong-phap §Nguồn-3.
2
+ technique: equivalence
3
+ description: Input chia thành nhóm cùng hành vi — chỉ cần 1 đại diện mỗi nhóm.
4
+ rule: "Gom giá trị theo vùng tương đương (valid / mỗi loại invalid) → 1 đại diện/vùng, tránh test tràn lan."
5
+ applies_to: [all]
6
+ note: "Không cần 300 email sai — chỉ 1 đại diện cho mỗi CÁCH sai (thiếu @, sai vị trí ., full-width…)."
@@ -0,0 +1,6 @@
1
+ # Technique: State Transition + Dependency State Coverage — from phuong-phap §Nguồn-3.
2
+ technique: state-transition
3
+ description: Đối tượng nhiều trạng thái; hoặc A phụ thuộc trạng thái của B.
4
+ rule: "Phủ chuyển hợp lệ + chuyển NGƯỢC không hợp lệ; test X với B ở đủ trạng thái (active/chưa active/hết hạn/xoá mềm)."
5
+ applies_to: [workflow-entity, dependent-feature]
6
+ example: "Draft → Submitted → Approved → Shipped (kể cả chuyển ngược không hợp lệ)."
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@sungen/driver-data-factory",
3
+ "version": "3.2.10-beta.11",
4
+ "description": "Sungen Data Factory capability — standardized test-data generation from a field-type + security knowledge catalog (valid/boundary/invalid + traceability), the .overwrite merge layer, and (opt-in) @dataFactory runtime seeding. Design-time knowledge is runtime-free. Plugs into @sun-asterisk/sungen via the capability SPI.",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "sungen": {
8
+ "capability": "data-factory"
9
+ },
10
+ "scripts": {
11
+ "build": "rm -rf dist && tsc -p tsconfig.json",
12
+ "test": "tsx tests/run.ts"
13
+ },
14
+ "author": "eqe team (engineer & quality) — Sun Asterisk",
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "@sun-asterisk/sungen": "3.2.10-beta.11",
18
+ "yaml": "^2.4.0"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src",
23
+ "common"
24
+ ]
25
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Data Factory catalog loader.
3
+ *
4
+ * Loads the SHIPPED common knowledge (`common/field-types`, `common/security`, `common/techniques`)
5
+ * and overlays the project's `qa/data-factory/common.overwrite.yaml` on top — later-wins, mirroring
6
+ * the env-overlay deepMerge already used by the runtime TestDataLoader (`specs-test-data.ts`).
7
+ *
8
+ * Update semantics: `sungen update` overwrites `common/**` (shipped, managed) but never touches
9
+ * `qa/data-factory/**` (user layer). A field-type entry with the same `id` in the overwrite REPLACES
10
+ * the shipped one; new ids are appended. Drift detection warns when an overwrite entry looks stale
11
+ * against the shipped common (so the user can prune it) — it never edits the overwrite itself.
12
+ */
13
+ import { readFileSync, readdirSync, existsSync, statSync } from 'fs';
14
+ import { join } from 'path';
15
+ import { createHash } from 'crypto';
16
+ import { parse as parseYaml } from 'yaml';
17
+ import type { Catalog, FieldType, SecurityBank, Technique, DataPoint, OverwriteWarning } from './types';
18
+
19
+ /** Resolve the packaged `common/` dir (works from src/ in dev via tsx and from dist/ when built). */
20
+ export function commonDir(): string {
21
+ // __dirname = .../driver-data-factory/{src|dist}; common is a sibling of both.
22
+ return join(__dirname, '..', 'common');
23
+ }
24
+
25
+ function readYamlFilesDeep(dir: string): Array<{ file: string; data: any }> {
26
+ const out: Array<{ file: string; data: any }> = [];
27
+ if (!existsSync(dir)) return out;
28
+ for (const name of readdirSync(dir)) {
29
+ const full = join(dir, name);
30
+ if (statSync(full).isDirectory()) out.push(...readYamlFilesDeep(full));
31
+ else if (name.endsWith('.yaml') || name.endsWith('.yml')) {
32
+ out.push({ file: full, data: parseYaml(readFileSync(full, 'utf8')) });
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+
38
+ /** Load the shipped common catalog (no project overlay). */
39
+ export function loadShippedCatalog(root = commonDir()): Catalog {
40
+ const fieldTypes: Record<string, FieldType> = {};
41
+ for (const { data } of readYamlFilesDeep(join(root, 'field-types'))) {
42
+ if (data?.type) fieldTypes[data.type] = data as FieldType;
43
+ }
44
+ const security: Record<string, SecurityBank> = {};
45
+ for (const { data } of readYamlFilesDeep(join(root, 'security'))) {
46
+ if (data?.class) security[data.class] = data as SecurityBank;
47
+ }
48
+ const techniques: Record<string, Technique> = {};
49
+ for (const { data } of readYamlFilesDeep(join(root, 'techniques'))) {
50
+ if (data?.technique) techniques[data.technique] = data as Technique;
51
+ }
52
+ return { fieldTypes, security, techniques };
53
+ }
54
+
55
+ /** Merge a field-type's group by DataPoint.id — overlay entries replace same-id, new ids append. */
56
+ function mergeGroup(base: DataPoint[] = [], overlay: DataPoint[] = []): DataPoint[] {
57
+ const byId = new Map<string, DataPoint>();
58
+ for (const dp of base) byId.set(dp.id, dp);
59
+ for (const dp of overlay) byId.set(dp.id, { ...byId.get(dp.id), ...dp });
60
+ return [...byId.values()];
61
+ }
62
+
63
+ function mergeFieldType(base: FieldType, overlay: Partial<FieldType>): FieldType {
64
+ return {
65
+ ...base,
66
+ ...overlay,
67
+ constraints: { ...(base.constraints || {}), ...(overlay.constraints || {}) },
68
+ uses_security: overlay.uses_security ?? base.uses_security,
69
+ valid: mergeGroup(base.valid, overlay.valid),
70
+ boundary: mergeGroup(base.boundary, overlay.boundary),
71
+ invalid: mergeGroup(base.invalid, overlay.invalid),
72
+ notes: [...(base.notes || []), ...(overlay.notes || [])],
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Load the effective catalog = shipped common + project overwrite overlay.
78
+ * `projectDir` is the QA repo root; overlay is read from `qa/data-factory/common.overwrite.yaml`.
79
+ */
80
+ export function loadEffectiveCatalog(projectDir: string, root = commonDir()): {
81
+ catalog: Catalog;
82
+ warnings: OverwriteWarning[];
83
+ } {
84
+ const shipped = loadShippedCatalog(root);
85
+ const overlayPath = join(projectDir, 'qa', 'data-factory', 'common.overwrite.yaml');
86
+ const warnings: OverwriteWarning[] = [];
87
+ if (!existsSync(overlayPath)) return { catalog: shipped, warnings };
88
+
89
+ const overlay = parseYaml(readFileSync(overlayPath, 'utf8')) || {};
90
+ const overlayFieldTypes: Record<string, Partial<FieldType>> = overlay.fieldTypes || overlay['field-types'] || {};
91
+ const merged: Catalog = {
92
+ fieldTypes: { ...shipped.fieldTypes },
93
+ security: { ...shipped.security },
94
+ techniques: { ...shipped.techniques },
95
+ };
96
+ for (const [type, ov] of Object.entries(overlayFieldTypes)) {
97
+ if (merged.fieldTypes[type]) {
98
+ merged.fieldTypes[type] = mergeFieldType(merged.fieldTypes[type], ov);
99
+ // Drift warning: overlay references a security bank the shipped common no longer has.
100
+ for (const bank of ov.uses_security || []) {
101
+ if (!merged.security[bank]) {
102
+ warnings.push({
103
+ fieldType: type,
104
+ detail: `overwrite tham chiếu security bank "${bank}" không còn trong common`,
105
+ suggestion: `cập nhật hoặc xoá mục "${type}" trong qa/data-factory/common.overwrite.yaml`,
106
+ });
107
+ }
108
+ }
109
+ } else {
110
+ // Wholly new field-type from the user — accept as-is (project-specific type).
111
+ merged.fieldTypes[type] = ov as FieldType;
112
+ }
113
+ }
114
+ return { catalog: merged, warnings };
115
+ }
116
+
117
+ /** Stable hash of the shipped common — recorded in df-manifest.json to detect update drift. */
118
+ export function shippedCommonHash(root = commonDir()): string {
119
+ const files = readYamlFilesDeep(root).sort((a, b) => a.file.localeCompare(b.file));
120
+ const h = createHash('sha256');
121
+ for (const { file, data } of files) h.update(file.split('/common/')[1] || file).update(JSON.stringify(data));
122
+ return h.digest('hex').slice(0, 16);
123
+ }