@sungen/driver-data-factory 3.2.10-beta.13 → 3.2.10-beta.15
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.
- package/common/field-types/currency.yaml +10 -10
- package/common/field-types/email.yaml +16 -16
- package/common/field-types/jp/katakana.yaml +12 -12
- package/common/field-types/number-float.yaml +10 -10
- package/common/field-types/number-integer.yaml +12 -12
- package/common/field-types/password.yaml +22 -22
- package/common/field-types/phone.yaml +14 -14
- package/common/field-types/postcode.yaml +8 -8
- package/common/field-types/string.yaml +11 -11
- package/common/security/broken-auth.yaml +6 -6
- package/common/security/idor.yaml +5 -5
- package/common/security/input-validation.yaml +5 -5
- package/common/security/sqli.yaml +7 -7
- package/common/security/upload.yaml +7 -7
- package/common/security/xss.yaml +9 -9
- package/common/techniques/boundary.yaml +4 -4
- package/common/techniques/decision-table.yaml +4 -4
- package/common/techniques/equivalence.yaml +4 -4
- package/common/techniques/state-transition.yaml +4 -4
- package/package.json +2 -2
- package/src/catalog-loader.ts +2 -2
- package/src/cli.ts +4 -0
- package/src/generator.ts +3 -3
- package/src/lint.ts +1 -1
- package/src/readme.ts +52 -0
- package/src/types.ts +1 -1
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# Field-type: currency (money) — from common-checklist.xlsx (Input box · Currency).
|
|
2
2
|
type: currency
|
|
3
|
-
description:
|
|
3
|
+
description: Money amount — integer/decimal per currency unit; watch leading-zero & the value 0.
|
|
4
4
|
constraints:
|
|
5
5
|
min: 0
|
|
6
6
|
max: 999999999 # placeholder
|
|
7
|
-
decimalPlaces: 0 # placeholder (JPY=0, USD=2
|
|
7
|
+
decimalPlaces: 0 # placeholder (JPY=0, USD=2 ...)
|
|
8
8
|
valid:
|
|
9
|
-
- { id: CHK-CUR-01, value: "1000", note: "
|
|
10
|
-
- { id: CHK-CUR-02, value: "0", note: "
|
|
9
|
+
- { id: CHK-CUR-01, value: "1000", note: "valid amount" }
|
|
10
|
+
- { id: CHK-CUR-02, value: "0", note: "0 must mean 0, NOT 'not entered'" }
|
|
11
11
|
boundary:
|
|
12
|
-
- { id: CHK-CUR-B1, gen: "
|
|
13
|
-
- { id: CHK-CUR-B2, gen: "
|
|
12
|
+
- { id: CHK-CUR-B1, gen: "min / min+1" }
|
|
13
|
+
- { id: CHK-CUR-B2, gen: "max / max-1" }
|
|
14
14
|
invalid:
|
|
15
|
-
- { id: CHK-CUR-I1, gen: "
|
|
16
|
-
- { id: CHK-CUR-I2, gen: "
|
|
17
|
-
- { id: CHK-CUR-I3, value: "", reason: "
|
|
15
|
+
- { id: CHK-CUR-I1, gen: "alphabetic / stray characters", reason: "not a number", errorRef: M13 }
|
|
16
|
+
- { id: CHK-CUR-I2, gen: "min-1 / max+1", reason: "out of bounds", errorRef: M13 }
|
|
17
|
+
- { id: CHK-CUR-I3, value: "", reason: "empty (per spec)", errorRef: M02 }
|
|
18
18
|
notes:
|
|
19
|
-
- "
|
|
19
|
+
- "Check separately: leading zero (0100) not stripped; thousands separator display format."
|
|
20
20
|
uses_security: [sqli]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Field-type: email — seeded from common-checklist.xlsx (Input box · Email) +
|
|
1
|
+
# Field-type: email — seeded from common-checklist.xlsx (Input box · Email) + the 4-source method.
|
|
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
|
|
3
|
+
# if the project spec has none, the value is marked as an open point, not invented.
|
|
4
4
|
type: email
|
|
5
|
-
description:
|
|
5
|
+
description: Half-width email, format <local-part>@<domain>.
|
|
6
6
|
format: "<local-part>@<domain>"
|
|
7
7
|
constraints:
|
|
8
8
|
charset: half-width
|
|
@@ -13,18 +13,18 @@ valid:
|
|
|
13
13
|
- { id: CHK-EMAIL-01, value: "user@example.com", note: "normal half-width" }
|
|
14
14
|
- { id: CHK-EMAIL-02, value: "u.name+tag@sub.example.co.jp", note: "dotted local + subdomain" }
|
|
15
15
|
boundary:
|
|
16
|
-
- { id: CHK-EMAIL-B1, gen: "local
|
|
17
|
-
- { id: CHK-EMAIL-B2, gen: "local
|
|
18
|
-
- { id: CHK-EMAIL-B3, gen: "
|
|
16
|
+
- { id: CHK-EMAIL-B1, gen: "local part = maxLocal", note: "local part = max (64)" }
|
|
17
|
+
- { id: CHK-EMAIL-B2, gen: "local part = maxLocal-1", note: "local part = max-1" }
|
|
18
|
+
- { id: CHK-EMAIL-B3, gen: "total = maxTotal", note: "total length = max (255)" }
|
|
19
19
|
invalid:
|
|
20
|
-
- { id: CHK-EMAIL-I1, value: "user@", reason: "
|
|
21
|
-
- { id: CHK-EMAIL-I2, value: "user@.com", reason: "
|
|
22
|
-
- { id: CHK-EMAIL-I3, value: "u@@b.com", reason: "
|
|
23
|
-
- { id: CHK-EMAIL-I4, value: "@example.com", reason: "
|
|
24
|
-
- { id: CHK-EMAIL-I5, value: "user@example.com", reason: "full-width
|
|
25
|
-
- { id: CHK-EMAIL-I6, gen: "local >
|
|
26
|
-
- { id: CHK-EMAIL-I7, gen: "
|
|
27
|
-
- { id: CHK-EMAIL-I8, value: "", reason: "
|
|
20
|
+
- { id: CHK-EMAIL-I1, value: "user@", reason: "missing domain", errorRef: M13 }
|
|
21
|
+
- { id: CHK-EMAIL-I2, value: "user@.com", reason: "dot in wrong position", errorRef: M13 }
|
|
22
|
+
- { id: CHK-EMAIL-I3, value: "u@@b.com", reason: "two @ signs", errorRef: M13 }
|
|
23
|
+
- { id: CHK-EMAIL-I4, value: "@example.com", reason: "missing local-part", errorRef: M13 }
|
|
24
|
+
- { id: CHK-EMAIL-I5, value: "user@example.com", reason: "full-width characters", errorRef: M39 }
|
|
25
|
+
- { id: CHK-EMAIL-I6, gen: "local > maxLocal", reason: "exceeds maxLocal", errorRef: M13 }
|
|
26
|
+
- { id: CHK-EMAIL-I7, gen: "total > maxTotal", reason: "exceeds maxTotal", errorRef: M13 }
|
|
27
|
+
- { id: CHK-EMAIL-I8, value: "", reason: "empty (required)", errorRef: M02 }
|
|
28
28
|
notes:
|
|
29
|
-
- "
|
|
30
|
-
- "Soft-delete:
|
|
29
|
+
- "Normalize: lowercase, NFKC, trim before comparison."
|
|
30
|
+
- "Soft-delete: if a deleted email gets a suffix appended, test maxTotal LOWER than the DB column limit to leave room for the suffix."
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# Field-type: Katakana name (JP-specific) — from
|
|
1
|
+
# Field-type: Katakana name (JP-specific) — from the 4-source method (JP field-type experience).
|
|
2
2
|
type: katakana
|
|
3
|
-
description:
|
|
3
|
+
description: Katakana-only field (kana name in Japanese systems).
|
|
4
4
|
constraints:
|
|
5
5
|
charset: "full-width Katakana only"
|
|
6
6
|
valid:
|
|
7
|
-
- { id: CHK-KANA-01, value: "ヤマダ", note: "Katakana
|
|
8
|
-
- { id: CHK-KANA-02, value: "ジョウジ", note: "
|
|
7
|
+
- { id: CHK-KANA-01, value: "ヤマダ", note: "standard Katakana" }
|
|
8
|
+
- { id: CHK-KANA-02, value: "ジョウジ", note: "valid dakuten + long-vowel mark" }
|
|
9
9
|
boundary:
|
|
10
|
-
- { id: CHK-KANA-B1, gen: "
|
|
10
|
+
- { id: CHK-KANA-B1, gen: "min / max length" }
|
|
11
11
|
invalid:
|
|
12
|
-
- { id: CHK-KANA-I1, value: "yamada", reason: "
|
|
13
|
-
- { id: CHK-KANA-I2, value: "やまだ", reason: "
|
|
14
|
-
- { id: CHK-KANA-I3, value: "山田", reason: "
|
|
15
|
-
- { id: CHK-KANA-I4, value: "ヤマダ", reason: "half-width kana (
|
|
16
|
-
- { id: CHK-KANA-I5, value: "ゝ々", reason: "
|
|
17
|
-
- { id: CHK-KANA-I6, value: "ャュョ", reason: "kana
|
|
12
|
+
- { id: CHK-KANA-I1, value: "yamada", reason: "Latin characters mixed in", errorRef: M39 }
|
|
13
|
+
- { id: CHK-KANA-I2, value: "やまだ", reason: "Hiragana mixed in", errorRef: M39 }
|
|
14
|
+
- { id: CHK-KANA-I3, value: "山田", reason: "Kanji mixed in", errorRef: M39 }
|
|
15
|
+
- { id: CHK-KANA-I4, value: "ヤマダ", reason: "half-width kana (if spec requires full-width)", errorRef: M39 }
|
|
16
|
+
- { id: CHK-KANA-I5, value: "ゝ々", reason: "iteration marks — real-world bug precedent in many systems", errorRef: M39 }
|
|
17
|
+
- { id: CHK-KANA-I6, value: "ャュョ", reason: "small kana standalone / wrong position", errorRef: M39 }
|
|
18
18
|
notes:
|
|
19
|
-
- "
|
|
19
|
+
- "Watch: small kana (ゃゅょ), long-vowel mark (ー), dakuten/handakuten, half<->full-width kana."
|
|
20
20
|
uses_security: [sqli, xss]
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# Field-type: number (float) — from common-checklist.xlsx (Input box · Number_Float).
|
|
2
2
|
type: number-float
|
|
3
|
-
description:
|
|
3
|
+
description: Decimal number — digits + decimal separator; decimal places per spec.
|
|
4
4
|
constraints:
|
|
5
5
|
min: 0.0 # placeholder
|
|
6
6
|
max: 999999.99 # placeholder
|
|
7
7
|
decimalPlaces: 2 # placeholder
|
|
8
8
|
valid:
|
|
9
|
-
- { id: CHK-FLOAT-01, value: "123.45", note: "
|
|
10
|
-
- { id: CHK-FLOAT-02, value: "0.5", note: "
|
|
9
|
+
- { id: CHK-FLOAT-01, value: "123.45", note: "valid" }
|
|
10
|
+
- { id: CHK-FLOAT-02, value: "0.5", note: "integer part = 0" }
|
|
11
11
|
boundary:
|
|
12
|
-
- { id: CHK-FLOAT-B1, gen: "
|
|
13
|
-
- { id: CHK-FLOAT-B2, gen: "
|
|
14
|
-
- { id: CHK-FLOAT-B3, gen: "
|
|
12
|
+
- { id: CHK-FLOAT-B1, gen: "min / min+1" }
|
|
13
|
+
- { id: CHK-FLOAT-B2, gen: "max / max-1" }
|
|
14
|
+
- { id: CHK-FLOAT-B3, gen: "exact decimal places / one extra digit" }
|
|
15
15
|
invalid:
|
|
16
|
-
- { id: CHK-FLOAT-I1, gen: "
|
|
17
|
-
- { id: CHK-FLOAT-I2, gen: "format
|
|
18
|
-
- { id: CHK-FLOAT-I3, gen: "
|
|
19
|
-
- { id: CHK-FLOAT-I4, value: "", reason: "
|
|
16
|
+
- { id: CHK-FLOAT-I1, gen: "alphabetic characters", reason: "not a number", errorRef: M13 }
|
|
17
|
+
- { id: CHK-FLOAT-I2, gen: "wrong format (multiple dots, +/- wrong position)", reason: "wrong format", errorRef: M13 }
|
|
18
|
+
- { id: CHK-FLOAT-I3, gen: "min-1 / max+1", reason: "out of bounds", errorRef: M13 }
|
|
19
|
+
- { id: CHK-FLOAT-I4, value: "", reason: "empty/multi-space (per spec)", errorRef: M02 }
|
|
20
20
|
uses_security: [sqli]
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# Field-type: number (integer) — from common-checklist.xlsx (Input box · Number_Integer).
|
|
2
2
|
type: number-integer
|
|
3
|
-
description:
|
|
3
|
+
description: Integer — digits only, optional +/- in valid position.
|
|
4
4
|
constraints:
|
|
5
|
-
min: 0 # placeholder
|
|
5
|
+
min: 0 # placeholder per spec
|
|
6
6
|
max: 2147483647 # placeholder
|
|
7
7
|
valid:
|
|
8
|
-
- { id: CHK-INT-01, value: "123", note: "
|
|
9
|
-
- { id: CHK-INT-02, value: "-45", note: "
|
|
8
|
+
- { id: CHK-INT-01, value: "123", note: "digits only" }
|
|
9
|
+
- { id: CHK-INT-02, value: "-45", note: "leading - (if spec allows negatives)" }
|
|
10
10
|
boundary:
|
|
11
|
-
- { id: CHK-INT-B1, gen: "
|
|
12
|
-
- { id: CHK-INT-B2, gen: "
|
|
11
|
+
- { id: CHK-INT-B1, gen: "min / min+1" }
|
|
12
|
+
- { id: CHK-INT-B2, gen: "max / max-1" }
|
|
13
13
|
invalid:
|
|
14
|
-
- { id: CHK-INT-I1, gen: "
|
|
15
|
-
- { id: CHK-INT-I2, gen: "
|
|
16
|
-
- { id: CHK-INT-I3, gen: "+/-
|
|
17
|
-
- { id: CHK-INT-I4, gen: "
|
|
18
|
-
- { id: CHK-INT-I5, value: "", reason: "
|
|
14
|
+
- { id: CHK-INT-I1, gen: "alphabetic characters", reason: "not a number", errorRef: M13 }
|
|
15
|
+
- { id: CHK-INT-I2, gen: "full-width (Japanese) digits", reason: "full-width characters", errorRef: M39 }
|
|
16
|
+
- { id: CHK-INT-I3, gen: "+/- in wrong position (mid-string)", reason: "wrong format", errorRef: M13 }
|
|
17
|
+
- { id: CHK-INT-I4, gen: "min-1 / max+1", reason: "out of bounds", errorRef: M13 }
|
|
18
|
+
- { id: CHK-INT-I5, value: "", reason: "empty/multi-space (per spec)", errorRef: M02 }
|
|
19
19
|
notes:
|
|
20
|
-
- "leading zero (007)
|
|
20
|
+
- "Check leading zero (007) is not stripped; redundant spaces before/after handled correctly."
|
|
21
21
|
uses_security: [sqli]
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
# Field-type: password —
|
|
1
|
+
# Field-type: password — from common-checklist.xlsx (Input box · Password).
|
|
2
2
|
type: password
|
|
3
|
-
description: Password —
|
|
4
|
-
format: "
|
|
3
|
+
description: Password — length + character-class rules per spec; plus show/hide + mask.
|
|
4
|
+
format: "per spec (min/max, >=1 upper/lower/digit as required)"
|
|
5
5
|
constraints:
|
|
6
|
-
minLength: 7 # placeholder —
|
|
6
|
+
minLength: 7 # placeholder — override with the project spec's real min
|
|
7
7
|
maxLength: 20 # placeholder
|
|
8
|
-
requires: [uppercase, lowercase, digit] # placeholder
|
|
8
|
+
requires: [uppercase, lowercase, digit] # placeholder per spec
|
|
9
9
|
uses_security: [sqli, xss]
|
|
10
10
|
valid:
|
|
11
|
-
- { id: CHK-PW-01, value: "Passw0rd", note: "
|
|
12
|
-
- { id: CHK-PW-02, note: "alpha only / numeric only / mix —
|
|
13
|
-
- { id: CHK-PW-03, note: "
|
|
11
|
+
- { id: CHK-PW-01, value: "Passw0rd", note: "upper+lower+digit, in range" }
|
|
12
|
+
- { id: CHK-PW-02, note: "alpha only / numeric only / mix — if the spec allows" }
|
|
13
|
+
- { id: CHK-PW-03, note: "valid special characters per spec (e.g. ._@)" }
|
|
14
14
|
boundary:
|
|
15
|
-
- { id: CHK-PW-B1, gen: "
|
|
16
|
-
- { id: CHK-PW-B2, gen: "
|
|
17
|
-
- { id: CHK-PW-B3, gen: "
|
|
18
|
-
- { id: CHK-PW-B4, gen: "
|
|
15
|
+
- { id: CHK-PW-B1, gen: "min length", note: "min (e.g. Aa1bcde = 7)" }
|
|
16
|
+
- { id: CHK-PW-B2, gen: "min+1", note: "min+1" }
|
|
17
|
+
- { id: CHK-PW-B3, gen: "max length", note: "max" }
|
|
18
|
+
- { id: CHK-PW-B4, gen: "max-1", note: "max-1" }
|
|
19
19
|
invalid:
|
|
20
|
-
- { id: CHK-PW-I1, value: "", reason: "
|
|
21
|
-
- { id: CHK-PW-I2, gen: "<
|
|
22
|
-
- { id: CHK-PW-I3, gen: ">
|
|
23
|
-
- { id: CHK-PW-I4, value: "passw0rd", reason: "
|
|
24
|
-
- { id: CHK-PW-I5, value: "PASSW0RD", reason: "
|
|
25
|
-
- { id: CHK-PW-I6, value: "Password", reason: "
|
|
26
|
-
- { id: CHK-PW-I7, gen: "full-width Japanese", reason: "
|
|
27
|
-
- { id: CHK-PW-I8, gen: "space before/after
|
|
20
|
+
- { id: CHK-PW-I1, value: "", reason: "empty/multi-space (required)", errorRef: M02 }
|
|
21
|
+
- { id: CHK-PW-I2, gen: "< min length", reason: "below minimum length", errorRef: M29 }
|
|
22
|
+
- { id: CHK-PW-I3, gen: "> max length", reason: "exceeds maximum length", errorRef: M30 }
|
|
23
|
+
- { id: CHK-PW-I4, value: "passw0rd", reason: "missing uppercase", errorRef: M31 }
|
|
24
|
+
- { id: CHK-PW-I5, value: "PASSW0RD", reason: "missing lowercase", errorRef: M32 }
|
|
25
|
+
- { id: CHK-PW-I6, value: "Password", reason: "missing digit", errorRef: M33 }
|
|
26
|
+
- { id: CHK-PW-I7, gen: "full-width Japanese", reason: "full-width characters", errorRef: M39 }
|
|
27
|
+
- { id: CHK-PW-I8, gen: "space before/after or inside", reason: "redundant whitespace", errorRef: M13 }
|
|
28
28
|
notes:
|
|
29
|
-
- "
|
|
30
|
-
- "Confirm Password:
|
|
29
|
+
- "Check separately: show/hide password icon (display + toggle mask<->unmask), mask symbol per spec."
|
|
30
|
+
- "Confirm Password: inherits all password cases + (confirm == password) and (confirm != password -> error)."
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
# Field-type: phone number — from common-checklist.xlsx (Input box · Phone number).
|
|
2
2
|
type: phone
|
|
3
|
-
description:
|
|
3
|
+
description: Phone number — digits only, allows () - + in valid positions.
|
|
4
4
|
constraints:
|
|
5
5
|
charset: "digits + () - +"
|
|
6
|
-
minLength: 8 # placeholder
|
|
6
|
+
minLength: 8 # placeholder per spec
|
|
7
7
|
maxLength: 15 # placeholder
|
|
8
8
|
valid:
|
|
9
|
-
- { id: CHK-PHONE-01, value: "0987654321", note: "
|
|
10
|
-
- { id: CHK-PHONE-02, value: "+84-90-1234567", note: "
|
|
11
|
-
- { id: CHK-PHONE-03, value: "(028)38221234", note: "
|
|
9
|
+
- { id: CHK-PHONE-01, value: "0987654321", note: "digits only" }
|
|
10
|
+
- { id: CHK-PHONE-02, value: "+84-90-1234567", note: "+ and - in valid positions" }
|
|
11
|
+
- { id: CHK-PHONE-03, value: "(028)38221234", note: "() in valid positions" }
|
|
12
12
|
boundary:
|
|
13
|
-
- { id: CHK-PHONE-B1, gen: "
|
|
14
|
-
- { id: CHK-PHONE-B2, gen: "
|
|
13
|
+
- { id: CHK-PHONE-B1, gen: "min / min+1 length" }
|
|
14
|
+
- { id: CHK-PHONE-B2, gen: "max / max-1 length" }
|
|
15
15
|
invalid:
|
|
16
|
-
- { id: CHK-PHONE-I1, gen: "
|
|
17
|
-
- { id: CHK-PHONE-I2, gen: "
|
|
18
|
-
- { id: CHK-PHONE-I3, gen: "
|
|
19
|
-
- { id: CHK-PHONE-I4, value: "090 123 4567", reason: "
|
|
20
|
-
- { id: CHK-PHONE-I5, gen: "+/-
|
|
21
|
-
- { id: CHK-PHONE-I6, gen: "min-1 / max+1", reason: "
|
|
22
|
-
- { id: CHK-PHONE-I7, value: "", reason: "
|
|
16
|
+
- { id: CHK-PHONE-I1, gen: "forbidden chars []:,;\"<>\\?", reason: "invalid characters", errorRef: M13 }
|
|
17
|
+
- { id: CHK-PHONE-I2, gen: "full-width (Japanese) digits", reason: "full-width characters", errorRef: M39 }
|
|
18
|
+
- { id: CHK-PHONE-I3, gen: "alphabetic characters", reason: "not a number", errorRef: M13 }
|
|
19
|
+
- { id: CHK-PHONE-I4, value: "090 123 4567", reason: "spaces between digits", errorRef: M13 }
|
|
20
|
+
- { id: CHK-PHONE-I5, gen: "+/- not first; () wrong position", reason: "wrong format", errorRef: M13 }
|
|
21
|
+
- { id: CHK-PHONE-I6, gen: "min-1 / max+1", reason: "out of bounds", errorRef: M13 }
|
|
22
|
+
- { id: CHK-PHONE-I7, value: "", reason: "empty/multi-space (per spec)", errorRef: M02 }
|
|
23
23
|
uses_security: [sqli]
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# Field-type: postcode / zipcode — from common-checklist.xlsx (Input box · Postcode/Zipcode).
|
|
2
2
|
type: postcode
|
|
3
|
-
description:
|
|
3
|
+
description: Postal code — region-specific format (JP 7 digits, optional -).
|
|
4
4
|
constraints:
|
|
5
|
-
format: "region-specific (
|
|
5
|
+
format: "region-specific (e.g. JP: NNN-NNNN)"
|
|
6
6
|
length: 7 # placeholder
|
|
7
7
|
valid:
|
|
8
|
-
- { id: CHK-ZIP-01, value: "1500001", note: "7
|
|
9
|
-
- { id: CHK-ZIP-02, value: "150-0001", note: "
|
|
8
|
+
- { id: CHK-ZIP-01, value: "1500001", note: "7 digits (JP)" }
|
|
9
|
+
- { id: CHK-ZIP-02, value: "150-0001", note: "with - if the spec allows" }
|
|
10
10
|
boundary:
|
|
11
|
-
- { id: CHK-ZIP-B1, gen: "
|
|
11
|
+
- { id: CHK-ZIP-B1, gen: "exact length / length-1 / +1" }
|
|
12
12
|
invalid:
|
|
13
|
-
- { id: CHK-ZIP-I1, gen: "
|
|
14
|
-
- { id: CHK-ZIP-I2, gen: "
|
|
15
|
-
- { id: CHK-ZIP-I3, value: "", reason: "
|
|
13
|
+
- { id: CHK-ZIP-I1, gen: "alphabetic / stray characters", reason: "invalid characters", errorRef: M13 }
|
|
14
|
+
- { id: CHK-ZIP-I2, gen: "wrong region format", reason: "wrong format", errorRef: M13 }
|
|
15
|
+
- { id: CHK-ZIP-I3, value: "", reason: "empty (per spec)", errorRef: M02 }
|
|
16
16
|
uses_security: [sqli]
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# Field-type: string (
|
|
1
|
+
# Field-type: string (single line) — from common-checklist.xlsx (Input box · String).
|
|
2
2
|
type: string
|
|
3
|
-
description:
|
|
3
|
+
description: Single-line string — accepts alnum, JP half/full-width, special chars, unicode, spaces.
|
|
4
4
|
constraints:
|
|
5
|
-
minLength: 1 # placeholder
|
|
5
|
+
minLength: 1 # placeholder per spec
|
|
6
6
|
maxLength: 255 # placeholder
|
|
7
7
|
uses_security: [sqli, xss]
|
|
8
8
|
valid:
|
|
9
9
|
- { id: CHK-STR-01, value: "Normal text 123", note: "alnum + space" }
|
|
10
|
-
- { id: CHK-STR-02, gen: "half-width Japanese", note: "
|
|
10
|
+
- { id: CHK-STR-02, gen: "half-width Japanese", note: "パカナ" }
|
|
11
11
|
- { id: CHK-STR-03, gen: "full-width Japanese", note: "全角かな漢字" }
|
|
12
|
-
- { id: CHK-STR-04, gen: "special chars ~!@#$%^&*()_+{}:|<>?,./;'[]-=`", note: "
|
|
13
|
-
- { id: CHK-STR-05, gen: "unicode / emoji
|
|
14
|
-
- { id: CHK-STR-06, gen: "copy-paste full-width JP", note: "
|
|
12
|
+
- { id: CHK-STR-04, gen: "special chars ~!@#$%^&*()_+{}:|<>?,./;'[]-=`", note: "valid special characters" }
|
|
13
|
+
- { id: CHK-STR-05, gen: "unicode / emoji if the spec allows" }
|
|
14
|
+
- { id: CHK-STR-06, gen: "copy-paste full-width JP", note: "must handle paste correctly" }
|
|
15
15
|
boundary:
|
|
16
|
-
- { id: CHK-STR-B1, gen: "
|
|
17
|
-
- { id: CHK-STR-B2, gen: "
|
|
16
|
+
- { id: CHK-STR-B1, gen: "min / min+1 length" }
|
|
17
|
+
- { id: CHK-STR-B2, gen: "max / max-1 length" }
|
|
18
18
|
invalid:
|
|
19
|
-
- { id: CHK-STR-I1, value: "", reason: "
|
|
20
|
-
- { id: CHK-STR-I2, gen: "min-1 / max+1", reason: "
|
|
19
|
+
- { id: CHK-STR-I1, value: "", reason: "empty/multi-space (success/fail per spec)", errorRef: M02 }
|
|
20
|
+
- { id: CHK-STR-I2, gen: "min-1 / max+1", reason: "out of bounds", errorRef: M13 }
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Security payload bank: Broken authentication — from common-checklist.xlsx (Broken authentication).
|
|
2
2
|
class: broken-auth
|
|
3
|
-
description:
|
|
3
|
+
description: Weak authentication — session/token/brute-force.
|
|
4
4
|
payloads:
|
|
5
|
-
- { id: CHK-SEC-AUTH-01, technique: "brute
|
|
6
|
-
- { id: CHK-SEC-AUTH-02, technique: "session
|
|
7
|
-
- { id: CHK-SEC-AUTH-03, technique: "session fixation —
|
|
8
|
-
- { id: CHK-SEC-AUTH-04, technique: "
|
|
9
|
-
oracle: "
|
|
5
|
+
- { id: CHK-SEC-AUTH-01, technique: "unlimited brute-force login", expect: "lockout/captcha/rate-limit present" }
|
|
6
|
+
- { id: CHK-SEC-AUTH-02, technique: "session not expired after logout / password change", expect: "old session invalidated" }
|
|
7
|
+
- { id: CHK-SEC-AUTH-03, technique: "session fixation — reuse the pre-login session id", expect: "issues a new session after login" }
|
|
8
|
+
- { id: CHK-SEC-AUTH-04, technique: "guessable / non-expiring token", expect: "strong random token with a TTL" }
|
|
9
|
+
oracle: "Unauthorized login / session persistence = a vulnerability."
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Security payload bank: IDOR — from common-checklist.xlsx (Insecure Direct Object Reference).
|
|
2
2
|
class: idor
|
|
3
|
-
description: Insecure Direct Object Reference —
|
|
3
|
+
description: Insecure Direct Object Reference — change an id to access another user's resource.
|
|
4
4
|
payloads:
|
|
5
|
-
- { id: CHK-SEC-IDOR-01, technique: "
|
|
6
|
-
- { id: CHK-SEC-IDOR-02, technique: "
|
|
7
|
-
- { id: CHK-SEC-IDOR-03, technique: "
|
|
8
|
-
oracle: "
|
|
5
|
+
- { id: CHK-SEC-IDOR-01, technique: "change the id in URL/param to another user's record", expect: "403/404, does NOT leak the other user's data" }
|
|
6
|
+
- { id: CHK-SEC-IDOR-02, technique: "guess sequential ids (1,2,3...) on a detail API", expect: "checks ownership, rejects" }
|
|
7
|
+
- { id: CHK-SEC-IDOR-03, technique: "act (edit/delete) on a resource outside your scope via id", expect: "blocked server-side, not just hidden in the UI" }
|
|
8
|
+
oracle: "Reading/modifying data outside your scope = a vulnerability. Expected: ownership authorization server-side."
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Security payload bank: Input validation — from common-checklist.xlsx (Input validation).
|
|
2
2
|
class: input-validation
|
|
3
|
-
description:
|
|
3
|
+
description: Server-side input constraints (not just the front-end).
|
|
4
4
|
payloads:
|
|
5
|
-
- { id: CHK-SEC-IV-01, technique: "
|
|
6
|
-
- { id: CHK-SEC-IV-02, technique: "
|
|
7
|
-
- { id: CHK-SEC-IV-03, technique: "control/null bytes,
|
|
8
|
-
oracle: "FE
|
|
5
|
+
- { id: CHK-SEC-IV-01, technique: "exceed boundary when the FE is bypassed (call the API directly)", expect: "the server rejects too" }
|
|
6
|
+
- { id: CHK-SEC-IV-02, technique: "wrong data type (string into a numeric field)", expect: "rejects with a valid error" }
|
|
7
|
+
- { id: CHK-SEC-IV-03, technique: "control/null bytes, control characters", expect: "stripped/rejected" }
|
|
8
|
+
oracle: "The FE may pass but the BE must reject — test the API directly, bypassing the FE."
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Security payload bank: SQL Injection — from common-checklist.xlsx (SQL Injection sheet).
|
|
2
2
|
# Reusable adversarial values injected into the `invalid` group of any field via uses_security: [sqli].
|
|
3
3
|
class: sqli
|
|
4
|
-
description: SQL Injection —
|
|
4
|
+
description: SQL Injection — inputs crafted to break the query / bypass authentication.
|
|
5
5
|
payloads:
|
|
6
|
-
- { id: CHK-SEC-SQLI-01, value: "admin' --", target: "login username", expect: "
|
|
7
|
-
- { id: CHK-SEC-SQLI-02, value: "test' OR '1'='1", target: "
|
|
8
|
-
- { id: CHK-SEC-SQLI-03, value: "test@test.com' OR '1'='1", target: "email
|
|
9
|
-
- { id: CHK-SEC-SQLI-04, value: "test', isAdmin = 1 --", target: "
|
|
10
|
-
- { id: CHK-SEC-SQLI-05, value: "2023-01-01' OR '1'='1", target: "
|
|
11
|
-
oracle: "
|
|
6
|
+
- { id: CHK-SEC-SQLI-01, value: "admin' --", target: "login username", expect: "must NOT log in bypassing the password" }
|
|
7
|
+
- { id: CHK-SEC-SQLI-02, value: "test' OR '1'='1", target: "search box", expect: "must NOT return all rows" }
|
|
8
|
+
- { id: CHK-SEC-SQLI-03, value: "test@test.com' OR '1'='1", target: "signup email", expect: "handled safely, no bad data stored" }
|
|
9
|
+
- { id: CHK-SEC-SQLI-04, value: "test', isAdmin = 1 --", target: "edit profile", expect: "must NOT escalate privileges" }
|
|
10
|
+
- { id: CHK-SEC-SQLI-05, value: "2023-01-01' OR '1'='1", target: "report filter", expect: "returns only the filtered data" }
|
|
11
|
+
oracle: "Any sign of bypass / wrong data returned = a vulnerability. Expected: escape/parametrize safely, return a valid error."
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Security payload bank: Upload file — from common-checklist.xlsx (Upload file function).
|
|
2
2
|
class: upload
|
|
3
|
-
description:
|
|
3
|
+
description: Malicious / constraint-breaking file upload.
|
|
4
4
|
payloads:
|
|
5
|
-
- { id: CHK-SEC-UP-01, technique: "
|
|
6
|
-
- { id: CHK-SEC-UP-02, technique: "MIME mismatch (
|
|
7
|
-
- { id: CHK-SEC-UP-03, technique: "oversize
|
|
8
|
-
- { id: CHK-SEC-UP-04, technique: "path traversal
|
|
9
|
-
- { id: CHK-SEC-UP-05, technique: "
|
|
10
|
-
oracle: "
|
|
5
|
+
- { id: CHK-SEC-UP-01, technique: "extension spoofing (shell.php.jpg)", expect: "checks real MIME + whitelist, rejects" }
|
|
6
|
+
- { id: CHK-SEC-UP-02, technique: "MIME mismatch (altered Content-Type)", expect: "verifies real content" }
|
|
7
|
+
- { id: CHK-SEC-UP-03, technique: "oversize beyond the limit", expect: "reports a size error" }
|
|
8
|
+
- { id: CHK-SEC-UP-04, technique: "path traversal in the filename (../../)", expect: "sanitizes the filename, stores in the right folder" }
|
|
9
|
+
- { id: CHK-SEC-UP-05, technique: "filename containing <script> / malicious metadata", expect: "handled safely, not rendered into the DOM" }
|
|
10
|
+
oracle: "Execution / write outside scope = a vulnerability."
|
package/common/security/xss.yaml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Security payload bank: XSS Injection — from common-checklist.xlsx (XSS Injection sheet).
|
|
2
2
|
class: xss
|
|
3
|
-
description: Cross-Site Scripting —
|
|
3
|
+
description: Cross-Site Scripting — inject script via input/URL/attribute/file to execute in the page.
|
|
4
4
|
payloads:
|
|
5
|
-
- { id: CHK-SEC-XSS-01, value: "<script>alert('XSS')</script>", kind: "Reflected/Stored", expect: "
|
|
6
|
-
- { id: CHK-SEC-XSS-02, value: "<img src=x onerror=alert(1)>", kind: "HTML attribute", expect: "
|
|
7
|
-
- { id: CHK-SEC-XSS-03, value: "<svg onload=alert(1)>", kind: "SVG payload", expect: "
|
|
8
|
-
- { id: CHK-SEC-XSS-04, value: "\"><script>alert(1)</script>", kind: "Attribute injection", expect: "
|
|
9
|
-
- { id: CHK-SEC-XSS-05, value: "<a href=\"javascript:alert(1)\">click me</a>", kind: "Dangerous link", expect: "href
|
|
10
|
-
- { id: CHK-SEC-XSS-06, value: "<script>alert(1)</script>.jpg", kind: "File name / metadata", expect: "
|
|
11
|
-
- { id: CHK-SEC-XSS-07, value: "<h1>test</h1>", kind: "HTML injection", expect: "
|
|
12
|
-
oracle: "
|
|
5
|
+
- { id: CHK-SEC-XSS-01, value: "<script>alert('XSS')</script>", kind: "Reflected/Stored", expect: "renders as plain text, does NOT execute the script" }
|
|
6
|
+
- { id: CHK-SEC-XSS-02, value: "<img src=x onerror=alert(1)>", kind: "HTML attribute", expect: "does not fire onerror" }
|
|
7
|
+
- { id: CHK-SEC-XSS-03, value: "<svg onload=alert(1)>", kind: "SVG payload", expect: "does not fire onload / strips the attribute" }
|
|
8
|
+
- { id: CHK-SEC-XSS-04, value: "\"><script>alert(1)</script>", kind: "Attribute injection", expect: "does not break the HTML, escapes safely" }
|
|
9
|
+
- { id: CHK-SEC-XSS-05, value: "<a href=\"javascript:alert(1)\">click me</a>", kind: "Dangerous link", expect: "href disabled / sanitized" }
|
|
10
|
+
- { id: CHK-SEC-XSS-06, value: "<script>alert(1)</script>.jpg", kind: "File name / metadata", expect: "filename handled safely, not rendered into the DOM" }
|
|
11
|
+
- { id: CHK-SEC-XSS-07, value: "<h1>test</h1>", kind: "HTML injection", expect: "displayed as text, not rendered as a tag" }
|
|
12
|
+
oracle: "An alert firing / a tag rendering = a vulnerability. Expected: escape/sanitize, no innerHTML/eval on user input."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Technique: Boundary Value Analysis — from
|
|
1
|
+
# Technique: Boundary Value Analysis — from the 4-source method.
|
|
2
2
|
technique: boundary
|
|
3
|
-
description:
|
|
4
|
-
rule: "
|
|
3
|
+
description: When a field has a clear min/max — generate values at and around the boundary.
|
|
4
|
+
rule: "For [min,max]: generate Min-1 (invalid), Min, Min+1, Max-1, Max, Max+1 (invalid)."
|
|
5
5
|
applies_to: [number-integer, number-float, currency, string, password, phone, postcode]
|
|
6
|
-
note: "
|
|
6
|
+
note: "Must include the exact boundary (Min, Max), not only over-boundary — off-by-one usually slips through here."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Technique: Decision Table — from
|
|
1
|
+
# Technique: Decision Table — from the 4-source method.
|
|
2
2
|
technique: decision-table
|
|
3
|
-
description:
|
|
4
|
-
rule: "
|
|
3
|
+
description: Multiple combined conditions affecting one outcome.
|
|
4
|
+
rule: "List condition combinations -> action; generate @cases from each rule row."
|
|
5
5
|
applies_to: [login, permission, business-flow]
|
|
6
|
-
example: "Login:
|
|
6
|
+
example: "Login: password correct/wrong x account locked/unlocked x 2FA on/off."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Technique: Equivalence Partitioning — from
|
|
1
|
+
# Technique: Equivalence Partitioning — from the 4-source method.
|
|
2
2
|
technique: equivalence
|
|
3
|
-
description:
|
|
4
|
-
rule: "
|
|
3
|
+
description: Inputs split into classes with the same behavior — one representative per class is enough.
|
|
4
|
+
rule: "Group values into equivalence classes (valid / each kind of invalid) -> one representative per class, avoid over-testing."
|
|
5
5
|
applies_to: [all]
|
|
6
|
-
note: "
|
|
6
|
+
note: "You don't need 300 bad emails — just one representative per WAY of being wrong (missing @, dot in wrong place, full-width...)."
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Technique: State Transition + Dependency State Coverage — from
|
|
1
|
+
# Technique: State Transition + Dependency State Coverage — from the 4-source method.
|
|
2
2
|
technique: state-transition
|
|
3
|
-
description:
|
|
4
|
-
rule: "
|
|
3
|
+
description: An object with multiple states; or A depends on the state of B.
|
|
4
|
+
rule: "Cover valid transitions + INVALID reverse transitions; test X with B in every state (active/inactive/expired/soft-deleted)."
|
|
5
5
|
applies_to: [workflow-entity, dependent-feature]
|
|
6
|
-
example: "Draft
|
|
6
|
+
example: "Draft -> Submitted -> Approved -> Shipped (including invalid reverse transitions)."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sungen/driver-data-factory",
|
|
3
|
-
"version": "3.2.10-beta.
|
|
3
|
+
"version": "3.2.10-beta.15",
|
|
4
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
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"author": "eqe team (engineer & quality) — Sun Asterisk",
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@sun-asterisk/sungen": "3.2.10-beta.
|
|
17
|
+
"@sun-asterisk/sungen": "3.2.10-beta.15",
|
|
18
18
|
"yaml": "^2.4.0"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
package/src/catalog-loader.ts
CHANGED
|
@@ -101,8 +101,8 @@ export function loadEffectiveCatalog(projectDir: string, root = commonDir()): {
|
|
|
101
101
|
if (!merged.security[bank]) {
|
|
102
102
|
warnings.push({
|
|
103
103
|
fieldType: type,
|
|
104
|
-
detail: `overwrite
|
|
105
|
-
suggestion: `
|
|
104
|
+
detail: `overwrite references security bank "${bank}" that no longer exists in the common catalog`,
|
|
105
|
+
suggestion: `update or remove the "${type}" entry in qa/data-factory/common.overwrite.yaml`,
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
}
|
package/src/cli.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { loadEffectiveCatalog } from './catalog-loader';
|
|
|
14
14
|
import { expandFieldMap, renderTestData, type FieldMap } from './generator';
|
|
15
15
|
import { lintFieldMap, summarize } from './lint';
|
|
16
16
|
import { generateFixture } from './fixture';
|
|
17
|
+
import { ensureDataFactoryReadme } from './readme';
|
|
17
18
|
|
|
18
19
|
const FIELDMAP_DIR = (cwd: string) => join(cwd, 'qa', 'data-factory');
|
|
19
20
|
const fieldMapPath = (cwd: string, name: string) => join(FIELDMAP_DIR(cwd), `${name}.fields.yaml`);
|
|
@@ -96,6 +97,7 @@ export function registerDataFactoryCommand(program: any): void {
|
|
|
96
97
|
.option('--force', 'Overwrite existing test-data without asking (implies --yes)')
|
|
97
98
|
.action(async (opts: { screen?: string; all?: boolean; yes?: boolean; force?: boolean }) => {
|
|
98
99
|
const cwd = process.cwd();
|
|
100
|
+
ensureDataFactoryReadme(cwd);
|
|
99
101
|
if (opts.screen) { await genOne(cwd, opts.screen, opts); return; }
|
|
100
102
|
if (!opts.all) {
|
|
101
103
|
console.error('Specify --screen <name> for one unit, or --all for every unit (with confirmation).');
|
|
@@ -123,6 +125,7 @@ export function registerDataFactoryCommand(program: any): void {
|
|
|
123
125
|
.option('--screen <name>', 'Lint one unit (default: all field-maps)')
|
|
124
126
|
.action((opts: { screen?: string }) => {
|
|
125
127
|
const cwd = process.cwd();
|
|
128
|
+
ensureDataFactoryReadme(cwd);
|
|
126
129
|
const names = opts.screen ? [opts.screen] : listFieldMaps(cwd);
|
|
127
130
|
if (names.length === 0) { console.error('No field-maps under qa/data-factory/*.fields.yaml.'); process.exitCode = 1; return; }
|
|
128
131
|
const { catalog } = loadEffectiveCatalog(cwd);
|
|
@@ -152,6 +155,7 @@ export function registerDataFactoryCommand(program: any): void {
|
|
|
152
155
|
.option('--force', 'Overwrite existing fixture file(s) without asking')
|
|
153
156
|
.action(async (opts: { screen: string; rows?: string; invalid?: boolean; force?: boolean }) => {
|
|
154
157
|
const cwd = process.cwd();
|
|
158
|
+
ensureDataFactoryReadme(cwd);
|
|
155
159
|
const map = loadFieldMap(cwd, opts.screen);
|
|
156
160
|
if (!map) { console.error(`✗ ${opts.screen}: no field-map at qa/data-factory/${opts.screen}.fields.yaml`); process.exitCode = 1; return; }
|
|
157
161
|
const { catalog } = loadEffectiveCatalog(cwd);
|
package/src/generator.ts
CHANGED
|
@@ -94,14 +94,14 @@ function synthesize(dp: DataPoint, type: string, c: Record<string, any>): { valu
|
|
|
94
94
|
if (dp.value != null) return { value: dp.value };
|
|
95
95
|
const hint = `${dp.gen || ''} ${dp.note || ''}`.toLowerCase();
|
|
96
96
|
|
|
97
|
-
// Email length boundaries. `>` / "
|
|
97
|
+
// Email length boundaries. `>` / "over" → over-boundary (invalid, offset +1).
|
|
98
98
|
if (type === 'email') {
|
|
99
|
-
const over = hint.includes('>') || hint.includes('
|
|
99
|
+
const over = hint.includes('>') || hint.includes('over');
|
|
100
100
|
const offset = over ? 1 : hint.includes('-1') ? -1 : hint.includes('+1') ? 1 : 0;
|
|
101
101
|
if (hint.includes('maxlocal') || (hint.includes('local') && (hint.includes('max') || over))) {
|
|
102
102
|
const v = synthEmail(c, 'maxLocal', offset); return v ? { value: v } : { value: null, todo: 'maxLocal' };
|
|
103
103
|
}
|
|
104
|
-
if (hint.includes('maxtotal') ||
|
|
104
|
+
if (hint.includes('maxtotal') || hint.includes('total')) {
|
|
105
105
|
const v = synthEmail(c, 'maxTotal', offset); return v ? { value: v } : { value: null, todo: 'maxTotal' };
|
|
106
106
|
}
|
|
107
107
|
}
|
package/src/lint.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Data Factory lint — the deterministic coverage oracle from phuong-phap-tao-test-data.md §6.
|
|
3
3
|
*
|
|
4
|
-
* For each field it confirms the "
|
|
4
|
+
* For each field it confirms the "is it enough" checklist: >=1 valid, boundary present when the field has a
|
|
5
5
|
* length/number bound, ≥1 invalid per rule, no unresolved values, error codes mapped (not left as
|
|
6
6
|
* placeholders). It also surfaces blind spots (type not in catalog) and — via the catalog loader —
|
|
7
7
|
* stale `.overwrite` warnings. Pure functions; no side effects.
|
package/src/readme.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drops a `qa/data-factory/README.md` into the user's project so the folder explains itself.
|
|
3
|
+
* Write-if-absent (never clobbers a user-edited README), like a config file.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
|
|
8
|
+
const README = `# \`qa/data-factory/\` — Data Factory (your project's test-data knowledge)
|
|
9
|
+
|
|
10
|
+
This folder is your project's **test-data standard**: machine-readable data rules that
|
|
11
|
+
\`sungen data gen\` / \`/sungen:create-data-test\` (and \`/sungen:create-test\` step 5.3) expand into
|
|
12
|
+
standardized test-data — **valid / boundary / invalid** values, each carrying a \`CHK-*\` trace id back
|
|
13
|
+
to the spec. It is a reusable asset that **survives \`sungen update\`** (only the driver's shipped
|
|
14
|
+
catalog is refreshed; nothing here is touched).
|
|
15
|
+
|
|
16
|
+
It is **not** a copy of your spec — it is the structured, reusable data-rules layer the deterministic
|
|
17
|
+
generator needs, complementary to \`requirements/spec.md\` (which is prose spec per screen/flow).
|
|
18
|
+
|
|
19
|
+
## What lives here
|
|
20
|
+
|
|
21
|
+
| File | What it is |
|
|
22
|
+
|---|---|
|
|
23
|
+
| \`<name>.fields.yaml\` | **Field-map** for a screen/flow — one entry per input field: its catalog \`type\`, the project's real \`constraints\`, an \`errorMap\` (catalog placeholder code → your real message code), and optionally \`unique: true\`, nested \`fields:\`, \`dependsOn:\`. Authored from \`requirements/spec.md\`; expanded deterministically. A unit with **no input fields** (navigation/list) has no field-map — that's correct. |
|
|
24
|
+
| \`common.overwrite.yaml\` | **Project-wide overrides** to the shipped catalog — your real length limits, real error codes, banned values, extra invalid cases. Deep-merged over the common (later-wins, keyed by \`type\` + data-point \`id\`). Genuinely new project knowledge the shipped catalog can't have. |
|
|
25
|
+
| \`project/<entity>.yaml\` | **Blind-spot answers** — business enums, cross-entity rules, data models the catalog can't know. You fill these when Data Factory hits a field \`type\` it doesn't have (instead of it guessing). |
|
|
26
|
+
| \`recipes.yaml\` | Seed recipes for the \`@dataFactory:<recipe>\` annotation — named field sets bound into \`{{recipe.*}}\` at runtime. |
|
|
27
|
+
|
|
28
|
+
## Good to know
|
|
29
|
+
|
|
30
|
+
- **\`sungen data gen\` merges** into \`test-data/<name>.yaml\` — it replaces only the field keys it owns
|
|
31
|
+
and preserves everything else (create-test data, flow-namespaced keys, \`@cases\`, overrides).
|
|
32
|
+
- **\`unique: true\`** makes a field's valid value fresh per run (\`{{$timestamp}}\`) so a register
|
|
33
|
+
email/username never collides — no cross-run cache.
|
|
34
|
+
- \`sungen data lint\` checks coverage; \`sungen data fixture\` builds a CSV import fixture under
|
|
35
|
+
\`qa/fixtures/\`.
|
|
36
|
+
- Edit the field-map / \`.overwrite\` and re-run — never hand-edit the generated \`test-data\` values.
|
|
37
|
+
|
|
38
|
+
Full guide: https://sungen.sun-asterisk.vn/docs/guides/data-factory
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
/** Ensure `qa/data-factory/README.md` exists (write-if-absent). */
|
|
42
|
+
export function ensureDataFactoryReadme(cwd: string): void {
|
|
43
|
+
const dir = join(cwd, 'qa', 'data-factory');
|
|
44
|
+
const path = join(dir, 'README.md');
|
|
45
|
+
if (existsSync(path)) return;
|
|
46
|
+
try {
|
|
47
|
+
mkdirSync(dir, { recursive: true });
|
|
48
|
+
writeFileSync(path, README, 'utf8');
|
|
49
|
+
} catch {
|
|
50
|
+
/* best-effort — a missing README must never fail a data command */
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -9,7 +9,7 @@ export interface DataPoint {
|
|
|
9
9
|
id: string;
|
|
10
10
|
/** Literal value (present for concrete cases). */
|
|
11
11
|
value?: string;
|
|
12
|
-
/** Human instruction to synthesize the value when it depends on project constraints (e.g. "local
|
|
12
|
+
/** Human instruction to synthesize the value when it depends on project constraints (e.g. "local part = maxLocal"). */
|
|
13
13
|
gen?: string;
|
|
14
14
|
/** Why this value is invalid (invalid group only). */
|
|
15
15
|
reason?: string;
|