hbsig 0.3.1 → 0.3.3
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/.babelrc-cjs +5 -0
- package/.babelrc-esm +5 -0
- package/README.md +1 -0
- package/dist/package.json +39 -0
- package/make.js +36 -0
- package/package.json +16 -16
- package/src/bin_to_str.js +46 -0
- package/src/collect-body-keys.js +436 -0
- package/src/commit.js +219 -0
- package/src/encode-array-item.js +112 -0
- package/src/encode-utils.js +191 -0
- package/src/encode.js +1256 -0
- package/src/erl_json.js +292 -0
- package/src/erl_str.js +1144 -0
- package/src/flat.js +250 -0
- package/src/http-message-signatures/httpbis.js +438 -0
- package/src/http-message-signatures/index.js +4 -0
- package/src/http-message-signatures/structured-header.js +105 -0
- package/src/httpsig.js +866 -0
- package/src/id.js +459 -0
- package/src/index.js +13 -0
- package/src/nocrypto.js +4 -0
- package/src/parser.js +171 -0
- package/src/send-utils.js +1132 -0
- package/src/send.js +142 -0
- package/src/signer-utils.js +375 -0
- package/src/signer.js +312 -0
- package/src/structured.js +496 -0
- package/src/test.js +2 -0
- package/src/utils.js +29 -0
- package/test/commit.test.js +41 -0
- package/test/erl_json.test.js +8 -0
- package/test/flat.test.js +27 -0
- package/test/httpsig.test.js +31 -0
- package/test/id.test.js +114 -0
- package/test/lib/all_cases.js +408 -0
- package/test/lib/cases.js +408 -0
- package/test/lib/erl_json_cases.js +161 -0
- package/test/lib/flat_cases.js +189 -0
- package/test/lib/gen.js +528 -0
- package/test/lib/httpsig_cases.js +313 -0
- package/test/lib/structured_cases.js +222 -0
- package/test/lib/test-utils.js +399 -0
- package/test/signer.test.js +48 -0
- package/test/structured.test.js +35 -0
- /package/{cjs → dist/cjs}/bin_to_str.js +0 -0
- /package/{cjs → dist/cjs}/collect-body-keys.js +0 -0
- /package/{cjs → dist/cjs}/commit.js +0 -0
- /package/{cjs → dist/cjs}/encode-array-item.js +0 -0
- /package/{cjs → dist/cjs}/encode-utils.js +0 -0
- /package/{cjs → dist/cjs}/encode.js +0 -0
- /package/{cjs → dist/cjs}/erl_json.js +0 -0
- /package/{cjs → dist/cjs}/erl_str.js +0 -0
- /package/{cjs → dist/cjs}/flat.js +0 -0
- /package/{cjs → dist/cjs}/http-message-signatures/httpbis.js +0 -0
- /package/{cjs → dist/cjs}/http-message-signatures/index.js +0 -0
- /package/{cjs → dist/cjs}/http-message-signatures/structured-header.js +0 -0
- /package/{cjs → dist/cjs}/httpsig.js +0 -0
- /package/{cjs → dist/cjs}/id.js +0 -0
- /package/{cjs → dist/cjs}/index.js +0 -0
- /package/{cjs → dist/cjs}/nocrypto.js +0 -0
- /package/{cjs → dist/cjs}/parser.js +0 -0
- /package/{cjs → dist/cjs}/send-utils.js +0 -0
- /package/{cjs → dist/cjs}/send.js +0 -0
- /package/{cjs → dist/cjs}/signer-utils.js +0 -0
- /package/{cjs → dist/cjs}/signer.js +0 -0
- /package/{cjs → dist/cjs}/structured.js +0 -0
- /package/{cjs → dist/cjs}/test.js +0 -0
- /package/{cjs → dist/cjs}/utils.js +0 -0
- /package/{esm → dist/esm}/bin_to_str.js +0 -0
- /package/{esm → dist/esm}/collect-body-keys.js +0 -0
- /package/{esm → dist/esm}/commit.js +0 -0
- /package/{esm → dist/esm}/encode-array-item.js +0 -0
- /package/{esm → dist/esm}/encode-utils.js +0 -0
- /package/{esm → dist/esm}/encode.js +0 -0
- /package/{esm → dist/esm}/erl_json.js +0 -0
- /package/{esm → dist/esm}/erl_str.js +0 -0
- /package/{esm → dist/esm}/flat.js +0 -0
- /package/{esm → dist/esm}/http-message-signatures/httpbis.js +0 -0
- /package/{esm → dist/esm}/http-message-signatures/index.js +0 -0
- /package/{esm → dist/esm}/http-message-signatures/structured-header.js +0 -0
- /package/{esm → dist/esm}/httpsig.js +0 -0
- /package/{esm → dist/esm}/id.js +0 -0
- /package/{esm → dist/esm}/index.js +0 -0
- /package/{esm → dist/esm}/nocrypto.js +0 -0
- /package/{esm → dist/esm}/package.json +0 -0
- /package/{esm → dist/esm}/parser.js +0 -0
- /package/{esm → dist/esm}/send-utils.js +0 -0
- /package/{esm → dist/esm}/send.js +0 -0
- /package/{esm → dist/esm}/signer-utils.js +0 -0
- /package/{esm → dist/esm}/signer.js +0 -0
- /package/{esm → dist/esm}/structured.js +0 -0
- /package/{esm → dist/esm}/test.js +0 -0
- /package/{esm → dist/esm}/utils.js +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Test cases for flat_from (flat to nested)
|
|
2
|
+
export const cases_from = [
|
|
3
|
+
// 1. Simple single-level key
|
|
4
|
+
{ a: "value" },
|
|
5
|
+
|
|
6
|
+
// 2. Single nested path
|
|
7
|
+
{ "a/b": "value" },
|
|
8
|
+
|
|
9
|
+
// 3. Multiple paths with same parent
|
|
10
|
+
{ "x/y": "1", "x/z": "2" },
|
|
11
|
+
|
|
12
|
+
// 4. Mixed levels
|
|
13
|
+
{ a: "1", "b/c": "2", "d/e/f": "3" },
|
|
14
|
+
|
|
15
|
+
// 5. Deep nesting
|
|
16
|
+
{ "a/b/c/d/e": "deep" },
|
|
17
|
+
|
|
18
|
+
// 6. Numeric values as strings (flat codec only handles strings)
|
|
19
|
+
{ count: "42", "nested/number": "3.14" },
|
|
20
|
+
|
|
21
|
+
// 7. Boolean values as strings
|
|
22
|
+
{ flag: "true", "config/enabled": "false" },
|
|
23
|
+
|
|
24
|
+
// 8. Null values as strings
|
|
25
|
+
{ nullable: "null", "deep/null/value": "null" },
|
|
26
|
+
|
|
27
|
+
// 9. Array values as nested paths (HB converts arrays to numbered maps)
|
|
28
|
+
// NOTE: HB flat codec preserves arrays as numbered sub-keys, not Erlang-style list strings
|
|
29
|
+
{ "list/1": "1", "list/2": "2", "list/3": "3", "nested/array/1": "a", "nested/array/2": "b", "nested/array/3": "c" },
|
|
30
|
+
|
|
31
|
+
// 10. Object values (these stay as objects when flattened)
|
|
32
|
+
{ "meta/type": "test", "config/data/key": "value" },
|
|
33
|
+
|
|
34
|
+
// 11. Special characters in values
|
|
35
|
+
{ message: "Hello, World!", "path/to/text": "Line1\nLine2" },
|
|
36
|
+
|
|
37
|
+
// 12. Empty string values
|
|
38
|
+
{ empty: "", "nested/empty": "" },
|
|
39
|
+
|
|
40
|
+
// 13. Unicode in keys and values
|
|
41
|
+
{ "unicode/🎉": "celebration", greeting: "Hello 世界" },
|
|
42
|
+
|
|
43
|
+
// 14. Numbers as strings
|
|
44
|
+
{ id: "123", "user/age": "25" },
|
|
45
|
+
|
|
46
|
+
// 15. Complex nested structure (all string values)
|
|
47
|
+
{
|
|
48
|
+
"app/name": "MyApp",
|
|
49
|
+
"app/version": "1.0.0",
|
|
50
|
+
"app/config/debug": "true",
|
|
51
|
+
"app/config/port": "3000",
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// 16. Path with numbers
|
|
55
|
+
{ "items/0": "first", "items/1": "second", "items/2": "third" },
|
|
56
|
+
|
|
57
|
+
// 17. Mixed data types (all converted to strings, arrays/objects to paths)
|
|
58
|
+
{
|
|
59
|
+
string: "text",
|
|
60
|
+
number: "42",
|
|
61
|
+
boolean: "true",
|
|
62
|
+
"null": "null",
|
|
63
|
+
"array/1": "1",
|
|
64
|
+
"array/2": "2",
|
|
65
|
+
"object/key": "val",
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// 18. Long path
|
|
69
|
+
{ "a/b/c/d/e/f/g/h/i/j": "deeply nested" },
|
|
70
|
+
|
|
71
|
+
// 19. Special JSON characters
|
|
72
|
+
{ json: '{"key": "value"}', "escaped/path": 'a"b"c' },
|
|
73
|
+
|
|
74
|
+
// 20. Large nested structure (all string values)
|
|
75
|
+
{
|
|
76
|
+
"users/john/name": "John Doe",
|
|
77
|
+
"users/john/email": "john@example.com",
|
|
78
|
+
"users/john/preferences/theme": "dark",
|
|
79
|
+
"users/john/preferences/notifications": "true",
|
|
80
|
+
"users/jane/name": "Jane Smith",
|
|
81
|
+
"users/jane/email": "jane@example.com",
|
|
82
|
+
"users/jane/preferences/theme": "light",
|
|
83
|
+
"users/jane/preferences/notifications": "false",
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
// Test cases for flat_to (nested to flat)
|
|
88
|
+
export const cases_to = [
|
|
89
|
+
// 1. Simple single-level object
|
|
90
|
+
{ a: "value" },
|
|
91
|
+
|
|
92
|
+
// 2. Single nested object
|
|
93
|
+
{ a: { b: "value" } },
|
|
94
|
+
|
|
95
|
+
// 3. Multiple properties at same level
|
|
96
|
+
{ x: { y: "1", z: "2" } },
|
|
97
|
+
|
|
98
|
+
// 4. Mixed levels
|
|
99
|
+
{ a: "1", b: { c: "2" }, d: { e: { f: "3" } } },
|
|
100
|
+
|
|
101
|
+
// 5. Deep nesting
|
|
102
|
+
{ a: { b: { c: { d: { e: "deep" } } } } },
|
|
103
|
+
|
|
104
|
+
// 6. Numeric values as strings
|
|
105
|
+
{ count: "42", nested: { number: "3.14" } },
|
|
106
|
+
|
|
107
|
+
// 7. Boolean values as strings
|
|
108
|
+
{ flag: "true", config: { enabled: "false" } },
|
|
109
|
+
|
|
110
|
+
// 8. Empty string values (not null)
|
|
111
|
+
{ nullable: "", deep: { empty: { value: "" } } },
|
|
112
|
+
|
|
113
|
+
// 9. Array values as JSON strings
|
|
114
|
+
{ list: "[1,2,3]", nested: { array: '["a","b","c"]' } },
|
|
115
|
+
|
|
116
|
+
// 10. Nested objects that should flatten completely
|
|
117
|
+
{ meta: { type: "test" }, config: { data: { key: "value" } } },
|
|
118
|
+
|
|
119
|
+
// 11. Special characters in values
|
|
120
|
+
{ message: "Hello, World!", path: { to: { text: "Line1\nLine2" } } },
|
|
121
|
+
|
|
122
|
+
// 12. Empty string values
|
|
123
|
+
{ empty: "", nested: { empty: "" } },
|
|
124
|
+
|
|
125
|
+
// 13. Unicode in keys and values
|
|
126
|
+
{ unicode: { "🎉": "celebration" }, greeting: "Hello 世界" },
|
|
127
|
+
|
|
128
|
+
// 14. Numbers as strings
|
|
129
|
+
{ id: "123", user: { age: "25" } },
|
|
130
|
+
|
|
131
|
+
// 15. Complex nested structure with string values
|
|
132
|
+
{
|
|
133
|
+
app: {
|
|
134
|
+
name: "MyApp",
|
|
135
|
+
version: "1.0.0",
|
|
136
|
+
config: {
|
|
137
|
+
debug: "true",
|
|
138
|
+
port: "3000",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// 16. Object with numeric-like keys
|
|
144
|
+
{ items: { 0: "first", 1: "second", 2: "third" } },
|
|
145
|
+
|
|
146
|
+
// 17. All string values at different levels
|
|
147
|
+
{
|
|
148
|
+
string: "text",
|
|
149
|
+
number: "42",
|
|
150
|
+
boolean: "true",
|
|
151
|
+
nullValue: "null",
|
|
152
|
+
array: "[1,2]",
|
|
153
|
+
object: '{"key":"val"}',
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// 18. Very deep nesting
|
|
157
|
+
{
|
|
158
|
+
a: {
|
|
159
|
+
b: {
|
|
160
|
+
c: { d: { e: { f: { g: { h: { i: { j: "deeply nested" } } } } } } },
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// 19. Special JSON characters
|
|
166
|
+
{ json: '{"key": "value"}', escaped: { path: 'a"b"c' } },
|
|
167
|
+
|
|
168
|
+
// 20. Large nested structure with all string values
|
|
169
|
+
{
|
|
170
|
+
users: {
|
|
171
|
+
john: {
|
|
172
|
+
name: "John Doe",
|
|
173
|
+
email: "john@example.com",
|
|
174
|
+
preferences: {
|
|
175
|
+
theme: "dark",
|
|
176
|
+
notifications: "true",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
jane: {
|
|
180
|
+
name: "Jane Smith",
|
|
181
|
+
email: "jane@example.com",
|
|
182
|
+
preferences: {
|
|
183
|
+
theme: "light",
|
|
184
|
+
notifications: "false",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
]
|
package/test/lib/gen.js
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate versatile JSON objects for fuzz testing the Erlang JSON codec
|
|
3
|
+
* This generator creates edge cases and various combinations to stress test the system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Helper function to get random element from array
|
|
7
|
+
function randomChoice(arr) {
|
|
8
|
+
return arr[Math.floor(Math.random() * arr.length)]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Helper function to generate random integer in range
|
|
12
|
+
function randomInt(min, max) {
|
|
13
|
+
return Math.floor(Math.random() * (max - min + 1)) + min
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Generate a random string with various edge cases
|
|
17
|
+
function generateString() {
|
|
18
|
+
const choices = [
|
|
19
|
+
// Empty string (edge case)
|
|
20
|
+
"",
|
|
21
|
+
// Single character
|
|
22
|
+
"a",
|
|
23
|
+
// ASCII printable
|
|
24
|
+
"Hello World",
|
|
25
|
+
// With special characters
|
|
26
|
+
"Line1\nLine2\tTab\rReturn",
|
|
27
|
+
// With quotes and backslashes
|
|
28
|
+
'He said "Hello" and \\escaped\\',
|
|
29
|
+
// Numbers as strings
|
|
30
|
+
"123",
|
|
31
|
+
"3.14",
|
|
32
|
+
"-42",
|
|
33
|
+
// Boolean-like strings
|
|
34
|
+
"true",
|
|
35
|
+
"false",
|
|
36
|
+
"null",
|
|
37
|
+
"undefined",
|
|
38
|
+
// Long string
|
|
39
|
+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(10),
|
|
40
|
+
// Unicode (though it will be converted to bytes)
|
|
41
|
+
"Hello 世界 😀",
|
|
42
|
+
// Special structured field patterns
|
|
43
|
+
":base64:",
|
|
44
|
+
// '%token%', // This causes issues - interpreted as atom marker
|
|
45
|
+
"::",
|
|
46
|
+
// '%%', // This causes issues - empty token marker
|
|
47
|
+
// Path-like
|
|
48
|
+
"/path/to/file.txt",
|
|
49
|
+
// URL-like
|
|
50
|
+
"https://example.com/path?query=value",
|
|
51
|
+
// JSON-like string
|
|
52
|
+
'{"key": "value"}',
|
|
53
|
+
// Array-like string
|
|
54
|
+
"[1, 2, 3]",
|
|
55
|
+
// With null bytes (will be encoded)
|
|
56
|
+
"before\x00after",
|
|
57
|
+
// All whitespace
|
|
58
|
+
" \t\n\r ",
|
|
59
|
+
// Mixed case
|
|
60
|
+
"CamelCase_snake_case-kebab-case",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
return randomChoice(choices)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Generate a random buffer with various patterns
|
|
67
|
+
function generateBuffer() {
|
|
68
|
+
const choices = [
|
|
69
|
+
// Empty buffer (edge case)
|
|
70
|
+
Buffer.alloc(0),
|
|
71
|
+
// Single byte
|
|
72
|
+
Buffer.from([0]),
|
|
73
|
+
Buffer.from([255]),
|
|
74
|
+
Buffer.from([128]),
|
|
75
|
+
// ASCII text
|
|
76
|
+
Buffer.from("Hello World"),
|
|
77
|
+
// Binary data
|
|
78
|
+
Buffer.from([0, 1, 2, 3, 255, 254, 253]),
|
|
79
|
+
// All zeros
|
|
80
|
+
Buffer.alloc(10),
|
|
81
|
+
// All 255s
|
|
82
|
+
Buffer.alloc(10, 255),
|
|
83
|
+
// Pattern
|
|
84
|
+
Buffer.from([0, 255, 0, 255, 0, 255]),
|
|
85
|
+
// Looks like UTF-8 but isn't valid
|
|
86
|
+
Buffer.from([0xff, 0xfe, 0xfd]),
|
|
87
|
+
// Valid UTF-8 for non-ASCII
|
|
88
|
+
Buffer.from("Hello 世界", "utf8"),
|
|
89
|
+
// Float bytes
|
|
90
|
+
Buffer.from(Float64Array.from([3.14159]).buffer),
|
|
91
|
+
// Large buffer
|
|
92
|
+
Buffer.alloc(1000, 42),
|
|
93
|
+
// Printable ASCII that looks like base64
|
|
94
|
+
Buffer.from("SGVsbG8gV29ybGQ="),
|
|
95
|
+
// Bytes that decode to special strings
|
|
96
|
+
Buffer.from("true"),
|
|
97
|
+
Buffer.from("null"),
|
|
98
|
+
Buffer.from("undefined"),
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
return randomChoice(choices)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Generate a random symbol - ONLY use global symbols or special ones
|
|
105
|
+
function generateSymbol() {
|
|
106
|
+
const choices = [
|
|
107
|
+
// Special symbols that convert to primitives
|
|
108
|
+
// These are handled specially by normalize()
|
|
109
|
+
Symbol("null"),
|
|
110
|
+
Symbol("true"),
|
|
111
|
+
Symbol("false"),
|
|
112
|
+
Symbol("undefined"),
|
|
113
|
+
// ALL other symbols MUST be global to round-trip properly
|
|
114
|
+
Symbol.for("ok"),
|
|
115
|
+
Symbol.for("error"),
|
|
116
|
+
Symbol.for("atom"),
|
|
117
|
+
Symbol.for("empty"),
|
|
118
|
+
Symbol.for("space"),
|
|
119
|
+
Symbol.for("simple"),
|
|
120
|
+
Symbol.for("with spaces"),
|
|
121
|
+
Symbol.for("with-dashes"),
|
|
122
|
+
Symbol.for("with_underscores"),
|
|
123
|
+
Symbol.for("123numeric"),
|
|
124
|
+
Symbol.for("CamelCase"),
|
|
125
|
+
Symbol.for("global"),
|
|
126
|
+
Symbol.for("also-global"),
|
|
127
|
+
Symbol.for('with"quotes'),
|
|
128
|
+
Symbol.for("with\\backslash"),
|
|
129
|
+
Symbol.for("with\nnewline"),
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
return randomChoice(choices)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Generate a random number (no Infinity, -Infinity, or NaN)
|
|
136
|
+
function generateNumber() {
|
|
137
|
+
const choices = [
|
|
138
|
+
// Integers
|
|
139
|
+
0,
|
|
140
|
+
1,
|
|
141
|
+
-1,
|
|
142
|
+
42,
|
|
143
|
+
255,
|
|
144
|
+
256,
|
|
145
|
+
-256,
|
|
146
|
+
// JavaScript number limits
|
|
147
|
+
Number.MAX_SAFE_INTEGER,
|
|
148
|
+
Number.MIN_SAFE_INTEGER,
|
|
149
|
+
9223372036854776000, // Close to max int64
|
|
150
|
+
-9223372036854776000,
|
|
151
|
+
// Floats
|
|
152
|
+
3.14,
|
|
153
|
+
-3.14,
|
|
154
|
+
0.1,
|
|
155
|
+
-0.1,
|
|
156
|
+
1.23e10,
|
|
157
|
+
1.23e-10,
|
|
158
|
+
// Edge cases
|
|
159
|
+
0.0,
|
|
160
|
+
-0.0,
|
|
161
|
+
0.999999999999999,
|
|
162
|
+
Number.EPSILON,
|
|
163
|
+
// Very large/small but not Infinity
|
|
164
|
+
1e308,
|
|
165
|
+
1e-308,
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
return randomChoice(choices)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Generate a random primitive value
|
|
172
|
+
// Note: Set excludeUndefined=true when using in object values since JSON can't serialize undefined
|
|
173
|
+
function generatePrimitive(excludeUndefined = false) {
|
|
174
|
+
const types = [
|
|
175
|
+
"string",
|
|
176
|
+
"buffer",
|
|
177
|
+
"number",
|
|
178
|
+
"boolean",
|
|
179
|
+
"null",
|
|
180
|
+
"symbol",
|
|
181
|
+
]
|
|
182
|
+
// Only include undefined if not excluded (undefined can't round-trip through JSON in objects)
|
|
183
|
+
if (!excludeUndefined) {
|
|
184
|
+
types.push("undefined")
|
|
185
|
+
}
|
|
186
|
+
const type = randomChoice(types)
|
|
187
|
+
|
|
188
|
+
switch (type) {
|
|
189
|
+
case "string":
|
|
190
|
+
return generateString()
|
|
191
|
+
case "buffer":
|
|
192
|
+
return generateBuffer()
|
|
193
|
+
case "number":
|
|
194
|
+
return generateNumber()
|
|
195
|
+
case "boolean":
|
|
196
|
+
return randomChoice([true, false])
|
|
197
|
+
case "null":
|
|
198
|
+
return null
|
|
199
|
+
case "undefined":
|
|
200
|
+
return undefined
|
|
201
|
+
case "symbol":
|
|
202
|
+
return generateSymbol()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Generate a random array
|
|
207
|
+
function generateArray(depth = 0, maxDepth = 3) {
|
|
208
|
+
if (depth >= maxDepth) {
|
|
209
|
+
return []
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const choices = [
|
|
213
|
+
// Empty array
|
|
214
|
+
() => [],
|
|
215
|
+
// Single element
|
|
216
|
+
() => [generatePrimitive()],
|
|
217
|
+
// Homogeneous arrays
|
|
218
|
+
() => [1, 2, 3, 4, 5],
|
|
219
|
+
() => ["a", "b", "c"],
|
|
220
|
+
() => [true, false, true],
|
|
221
|
+
() => [Buffer.from("a"), Buffer.from("b"), Buffer.from("c")],
|
|
222
|
+
// Mixed types - use generateSymbol() for symbols
|
|
223
|
+
() => [1, "two", Buffer.from("three"), true, null, generateSymbol()],
|
|
224
|
+
// With undefined (which should be preserved in arrays)
|
|
225
|
+
// Actually, undefined in arrays causes issues with JSON serialization
|
|
226
|
+
// () => [1, undefined, 3],
|
|
227
|
+
// Nested arrays
|
|
228
|
+
() =>
|
|
229
|
+
depth < maxDepth - 1
|
|
230
|
+
? [
|
|
231
|
+
[1, 2],
|
|
232
|
+
[3, 4],
|
|
233
|
+
[5, 6],
|
|
234
|
+
]
|
|
235
|
+
: [1, 2, 3],
|
|
236
|
+
// Array with objects
|
|
237
|
+
() => (depth < maxDepth - 1 ? [{ a: 1 }, { b: 2 }, { c: 3 }] : [1, 2, 3]),
|
|
238
|
+
// Large array
|
|
239
|
+
() => Array(100).fill(42),
|
|
240
|
+
// Array with all types - use generators
|
|
241
|
+
() => [
|
|
242
|
+
0,
|
|
243
|
+
-1,
|
|
244
|
+
3.14,
|
|
245
|
+
"",
|
|
246
|
+
"hello",
|
|
247
|
+
Buffer.alloc(0),
|
|
248
|
+
Buffer.from("data"),
|
|
249
|
+
true,
|
|
250
|
+
false,
|
|
251
|
+
null,
|
|
252
|
+
undefined,
|
|
253
|
+
generateSymbol(),
|
|
254
|
+
generateSymbol(),
|
|
255
|
+
[],
|
|
256
|
+
{},
|
|
257
|
+
],
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
const choice = randomChoice(choices)
|
|
261
|
+
const arr = choice()
|
|
262
|
+
|
|
263
|
+
// Sometimes add nested structures
|
|
264
|
+
if (depth < maxDepth - 1 && Math.random() < 0.3) {
|
|
265
|
+
arr.push(generateArray(depth + 1, maxDepth))
|
|
266
|
+
arr.push(generateObject(depth + 1, maxDepth))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return arr
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Generate a random object
|
|
273
|
+
function generateObject(depth = 0, maxDepth = 3) {
|
|
274
|
+
if (depth >= maxDepth) {
|
|
275
|
+
return {}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const choices = [
|
|
279
|
+
// Empty object
|
|
280
|
+
() => ({}),
|
|
281
|
+
// Single key - exclude undefined since JSON can't serialize it
|
|
282
|
+
() => ({ key: generatePrimitive(true) }),
|
|
283
|
+
// Multiple keys with same type
|
|
284
|
+
() => ({ a: 1, b: 2, c: 3 }),
|
|
285
|
+
() => ({ x: "hello", y: "world", z: "test" }),
|
|
286
|
+
// Mixed types - use generators
|
|
287
|
+
() => ({
|
|
288
|
+
string: "hello",
|
|
289
|
+
number: 42,
|
|
290
|
+
float: 3.14,
|
|
291
|
+
buffer: Buffer.from("data"),
|
|
292
|
+
bool: true,
|
|
293
|
+
nil: null,
|
|
294
|
+
symbol: generateSymbol(),
|
|
295
|
+
}),
|
|
296
|
+
// NOTE: Removed test case with undefined since JSON can't serialize undefined in objects
|
|
297
|
+
// () => ({ a: 1, b: undefined, c: 3 }),
|
|
298
|
+
// Nested objects
|
|
299
|
+
() =>
|
|
300
|
+
depth < maxDepth - 1
|
|
301
|
+
? {
|
|
302
|
+
user: {
|
|
303
|
+
name: "Alice",
|
|
304
|
+
age: 30,
|
|
305
|
+
data: Buffer.from("userdata"),
|
|
306
|
+
},
|
|
307
|
+
}
|
|
308
|
+
: { name: "Alice" },
|
|
309
|
+
// Keys that test normalization
|
|
310
|
+
() => ({ CamelCase: 1, lowercase: 2, UPPERCASE: 3 }),
|
|
311
|
+
// Numeric string keys
|
|
312
|
+
() => ({ 0: "zero", 1: "one", 2: "two" }),
|
|
313
|
+
// Special key names
|
|
314
|
+
// NOTE: Removed ao-types test - it's reserved for type annotations in TABM format
|
|
315
|
+
() => ({
|
|
316
|
+
"content-type": "application/json",
|
|
317
|
+
$empty: "should not be special",
|
|
318
|
+
}),
|
|
319
|
+
// Deep nesting
|
|
320
|
+
() =>
|
|
321
|
+
depth === 0
|
|
322
|
+
? {
|
|
323
|
+
a: { b: { c: { d: { e: "deep" } } } },
|
|
324
|
+
}
|
|
325
|
+
: { shallow: true },
|
|
326
|
+
// Large object
|
|
327
|
+
() =>
|
|
328
|
+
Object.fromEntries(
|
|
329
|
+
Array(50)
|
|
330
|
+
.fill(0)
|
|
331
|
+
.map((_, i) => [`key${i}`, i])
|
|
332
|
+
),
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
const choice = randomChoice(choices)
|
|
336
|
+
const obj = choice()
|
|
337
|
+
|
|
338
|
+
// Sometimes add nested structures
|
|
339
|
+
if (depth < maxDepth - 1 && Math.random() < 0.3) {
|
|
340
|
+
obj.nested_array = generateArray(depth + 1, maxDepth)
|
|
341
|
+
obj.nested_object = generateObject(depth + 1, maxDepth)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return obj
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Generate a complex test case
|
|
348
|
+
function generateTestCase() {
|
|
349
|
+
const choices = [
|
|
350
|
+
// NOTE: Primitives are NOT valid TABM structures - they cause function_clause errors
|
|
351
|
+
// in dev_codec_structured. TABM only supports objects (maps) and arrays (lists).
|
|
352
|
+
// Arrays
|
|
353
|
+
() => generateArray(),
|
|
354
|
+
// Objects
|
|
355
|
+
() => generateObject(),
|
|
356
|
+
// Complex nested structure - use generators for symbols
|
|
357
|
+
() => ({
|
|
358
|
+
metadata: {
|
|
359
|
+
version: "1.0",
|
|
360
|
+
timestamp: Date.now(),
|
|
361
|
+
flags: Buffer.from([0xff, 0x00, 0xff]),
|
|
362
|
+
},
|
|
363
|
+
data: {
|
|
364
|
+
users: [
|
|
365
|
+
{ id: 1, name: "Alice", avatar: Buffer.from("avatar1") },
|
|
366
|
+
{ id: 2, name: "Bob", avatar: Buffer.from("avatar2") },
|
|
367
|
+
],
|
|
368
|
+
settings: {
|
|
369
|
+
enabled: true,
|
|
370
|
+
threshold: 0.95,
|
|
371
|
+
mode: generateSymbol(),
|
|
372
|
+
options: [null, undefined, "", []],
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
_internal: {
|
|
376
|
+
cache: {},
|
|
377
|
+
buffer: Buffer.alloc(1024),
|
|
378
|
+
symbols: Array(3)
|
|
379
|
+
.fill(null)
|
|
380
|
+
.map(() => generateSymbol()),
|
|
381
|
+
},
|
|
382
|
+
}),
|
|
383
|
+
// Edge case: circular reference prevention test
|
|
384
|
+
() => {
|
|
385
|
+
const obj = { a: 1 }
|
|
386
|
+
obj.b = { c: 2, d: { e: 3 } } // Deep but not circular
|
|
387
|
+
return obj
|
|
388
|
+
},
|
|
389
|
+
// All empty values
|
|
390
|
+
() => ({
|
|
391
|
+
empty_string: "",
|
|
392
|
+
empty_buffer: Buffer.alloc(0),
|
|
393
|
+
empty_array: [],
|
|
394
|
+
empty_object: {},
|
|
395
|
+
null_value: null,
|
|
396
|
+
undefined_value: undefined,
|
|
397
|
+
}),
|
|
398
|
+
// Number edge cases (no Infinity, -Infinity, NaN)
|
|
399
|
+
() => ({
|
|
400
|
+
integers: [0, -0, 1, -1, 1000000, -1000000],
|
|
401
|
+
floats: [0.0, -0.0, 0.1, -0.1, 3.14159, -3.14159],
|
|
402
|
+
scientific: [1e10, 1e-10, 1.23e45, -1.23e-45],
|
|
403
|
+
}),
|
|
404
|
+
// String edge cases
|
|
405
|
+
() => ({
|
|
406
|
+
strings: [
|
|
407
|
+
"",
|
|
408
|
+
" ",
|
|
409
|
+
"\n",
|
|
410
|
+
"\t",
|
|
411
|
+
"\r\n",
|
|
412
|
+
"null",
|
|
413
|
+
"true",
|
|
414
|
+
"false",
|
|
415
|
+
"undefined",
|
|
416
|
+
"0",
|
|
417
|
+
"[]",
|
|
418
|
+
"{}",
|
|
419
|
+
'"quoted"',
|
|
420
|
+
"'quoted'",
|
|
421
|
+
"\\escaped\\",
|
|
422
|
+
"multi\nline\nstring",
|
|
423
|
+
"../../path/traversal",
|
|
424
|
+
'<script>alert("xss")</script>',
|
|
425
|
+
'"; DROP TABLE users; --',
|
|
426
|
+
"\x00\x01\x02\x03",
|
|
427
|
+
],
|
|
428
|
+
}),
|
|
429
|
+
// Buffer patterns
|
|
430
|
+
() => ({
|
|
431
|
+
buffers: [
|
|
432
|
+
Buffer.alloc(0),
|
|
433
|
+
Buffer.alloc(1, 0),
|
|
434
|
+
Buffer.alloc(1, 255),
|
|
435
|
+
Buffer.from([0, 127, 128, 255]),
|
|
436
|
+
Buffer.from("Hello World"),
|
|
437
|
+
Buffer.from("00000000", "hex"),
|
|
438
|
+
Buffer.from("FFFFFFFF", "hex"),
|
|
439
|
+
Buffer.from("DEADBEEF", "hex"),
|
|
440
|
+
Buffer.from([0x00, 0x00, 0x00, 0x00]),
|
|
441
|
+
Buffer.concat([
|
|
442
|
+
Buffer.from("Hello"),
|
|
443
|
+
Buffer.from(" "),
|
|
444
|
+
Buffer.from("World"),
|
|
445
|
+
]),
|
|
446
|
+
],
|
|
447
|
+
}),
|
|
448
|
+
// Symbol variations - use generator
|
|
449
|
+
() => ({
|
|
450
|
+
symbols: Array(10)
|
|
451
|
+
.fill(null)
|
|
452
|
+
.map(() => generateSymbol()),
|
|
453
|
+
}),
|
|
454
|
+
]
|
|
455
|
+
|
|
456
|
+
return randomChoice(choices)()
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Main generator function
|
|
460
|
+
export function gen(count = 100) {
|
|
461
|
+
const cases = []
|
|
462
|
+
|
|
463
|
+
// Ensure we get a good distribution of different types
|
|
464
|
+
const minPerType = Math.floor(count / 10)
|
|
465
|
+
|
|
466
|
+
// Add some guaranteed edge cases
|
|
467
|
+
// NOTE: Bare primitives are NOT valid TABM structures - only objects and arrays work.
|
|
468
|
+
// Primitives like null, true, false, 0, "", Buffer cause function_clause errors.
|
|
469
|
+
const guaranteedCases = [
|
|
470
|
+
// Empty collections
|
|
471
|
+
[],
|
|
472
|
+
{},
|
|
473
|
+
// Simple collections
|
|
474
|
+
[1, 2, 3],
|
|
475
|
+
{ a: 1, b: 2 },
|
|
476
|
+
// Mixed arrays - use global symbols
|
|
477
|
+
[null, undefined, 0, "", Buffer.alloc(0), [], {}],
|
|
478
|
+
// Nested empty
|
|
479
|
+
{ a: { b: { c: {} } } },
|
|
480
|
+
[[[[[]]]]],
|
|
481
|
+
// All types in one object - use global symbol
|
|
482
|
+
{
|
|
483
|
+
str: "string",
|
|
484
|
+
num: 42,
|
|
485
|
+
float: 3.14,
|
|
486
|
+
bool_t: true,
|
|
487
|
+
bool_f: false,
|
|
488
|
+
nil: null,
|
|
489
|
+
undef: undefined,
|
|
490
|
+
buf: Buffer.from("buffer"),
|
|
491
|
+
arr: [1, 2, 3],
|
|
492
|
+
obj: { nested: true },
|
|
493
|
+
sym: Symbol.for("symbol"),
|
|
494
|
+
},
|
|
495
|
+
]
|
|
496
|
+
|
|
497
|
+
// Add guaranteed cases
|
|
498
|
+
guaranteedCases.forEach(c => {
|
|
499
|
+
if (cases.length < count) {
|
|
500
|
+
cases.push(c)
|
|
501
|
+
}
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
// Generate random cases for the rest
|
|
505
|
+
while (cases.length < count) {
|
|
506
|
+
cases.push(generateTestCase())
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Shuffle the array to mix guaranteed and random cases
|
|
510
|
+
for (let i = cases.length - 1; i > 0; i--) {
|
|
511
|
+
const j = Math.floor(Math.random() * (i + 1))
|
|
512
|
+
;[cases[i], cases[j]] = [cases[j], cases[i]]
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return cases
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Export individual generators for testing
|
|
519
|
+
export {
|
|
520
|
+
generateString,
|
|
521
|
+
generateBuffer,
|
|
522
|
+
generateSymbol,
|
|
523
|
+
generateNumber,
|
|
524
|
+
generatePrimitive,
|
|
525
|
+
generateArray,
|
|
526
|
+
generateObject,
|
|
527
|
+
generateTestCase,
|
|
528
|
+
}
|