hbsig 0.3.2 → 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.
Files changed (95) hide show
  1. package/.babelrc-cjs +5 -0
  2. package/.babelrc-esm +5 -0
  3. package/README.md +1 -0
  4. package/dist/package.json +39 -0
  5. package/make.js +36 -0
  6. package/package.json +16 -17
  7. package/src/bin_to_str.js +46 -0
  8. package/src/collect-body-keys.js +436 -0
  9. package/src/commit.js +219 -0
  10. package/src/encode-array-item.js +112 -0
  11. package/src/encode-utils.js +191 -0
  12. package/src/encode.js +1256 -0
  13. package/src/erl_json.js +292 -0
  14. package/src/erl_str.js +1144 -0
  15. package/src/flat.js +250 -0
  16. package/src/http-message-signatures/httpbis.js +438 -0
  17. package/src/http-message-signatures/index.js +4 -0
  18. package/src/http-message-signatures/structured-header.js +105 -0
  19. package/src/httpsig.js +866 -0
  20. package/src/id.js +459 -0
  21. package/src/index.js +13 -0
  22. package/src/nocrypto.js +4 -0
  23. package/src/parser.js +171 -0
  24. package/src/send-utils.js +1132 -0
  25. package/src/send.js +142 -0
  26. package/src/signer-utils.js +375 -0
  27. package/src/signer.js +312 -0
  28. package/src/structured.js +496 -0
  29. package/src/test.js +2 -0
  30. package/src/utils.js +29 -0
  31. package/test/commit.test.js +41 -0
  32. package/test/erl_json.test.js +8 -0
  33. package/test/flat.test.js +27 -0
  34. package/test/httpsig.test.js +31 -0
  35. package/test/id.test.js +114 -0
  36. package/test/lib/all_cases.js +408 -0
  37. package/test/lib/cases.js +408 -0
  38. package/test/lib/erl_json_cases.js +161 -0
  39. package/test/lib/flat_cases.js +189 -0
  40. package/test/lib/gen.js +528 -0
  41. package/test/lib/httpsig_cases.js +313 -0
  42. package/test/lib/structured_cases.js +222 -0
  43. package/test/lib/test-utils.js +399 -0
  44. package/test/signer.test.js +48 -0
  45. package/test/structured.test.js +35 -0
  46. package/bin/install-deps +0 -0
  47. /package/{cjs → dist/cjs}/bin_to_str.js +0 -0
  48. /package/{cjs → dist/cjs}/collect-body-keys.js +0 -0
  49. /package/{cjs → dist/cjs}/commit.js +0 -0
  50. /package/{cjs → dist/cjs}/encode-array-item.js +0 -0
  51. /package/{cjs → dist/cjs}/encode-utils.js +0 -0
  52. /package/{cjs → dist/cjs}/encode.js +0 -0
  53. /package/{cjs → dist/cjs}/erl_json.js +0 -0
  54. /package/{cjs → dist/cjs}/erl_str.js +0 -0
  55. /package/{cjs → dist/cjs}/flat.js +0 -0
  56. /package/{cjs → dist/cjs}/http-message-signatures/httpbis.js +0 -0
  57. /package/{cjs → dist/cjs}/http-message-signatures/index.js +0 -0
  58. /package/{cjs → dist/cjs}/http-message-signatures/structured-header.js +0 -0
  59. /package/{cjs → dist/cjs}/httpsig.js +0 -0
  60. /package/{cjs → dist/cjs}/id.js +0 -0
  61. /package/{cjs → dist/cjs}/index.js +0 -0
  62. /package/{cjs → dist/cjs}/nocrypto.js +0 -0
  63. /package/{cjs → dist/cjs}/parser.js +0 -0
  64. /package/{cjs → dist/cjs}/send-utils.js +0 -0
  65. /package/{cjs → dist/cjs}/send.js +0 -0
  66. /package/{cjs → dist/cjs}/signer-utils.js +0 -0
  67. /package/{cjs → dist/cjs}/signer.js +0 -0
  68. /package/{cjs → dist/cjs}/structured.js +0 -0
  69. /package/{cjs → dist/cjs}/test.js +0 -0
  70. /package/{cjs → dist/cjs}/utils.js +0 -0
  71. /package/{esm → dist/esm}/bin_to_str.js +0 -0
  72. /package/{esm → dist/esm}/collect-body-keys.js +0 -0
  73. /package/{esm → dist/esm}/commit.js +0 -0
  74. /package/{esm → dist/esm}/encode-array-item.js +0 -0
  75. /package/{esm → dist/esm}/encode-utils.js +0 -0
  76. /package/{esm → dist/esm}/encode.js +0 -0
  77. /package/{esm → dist/esm}/erl_json.js +0 -0
  78. /package/{esm → dist/esm}/erl_str.js +0 -0
  79. /package/{esm → dist/esm}/flat.js +0 -0
  80. /package/{esm → dist/esm}/http-message-signatures/httpbis.js +0 -0
  81. /package/{esm → dist/esm}/http-message-signatures/index.js +0 -0
  82. /package/{esm → dist/esm}/http-message-signatures/structured-header.js +0 -0
  83. /package/{esm → dist/esm}/httpsig.js +0 -0
  84. /package/{esm → dist/esm}/id.js +0 -0
  85. /package/{esm → dist/esm}/index.js +0 -0
  86. /package/{esm → dist/esm}/nocrypto.js +0 -0
  87. /package/{esm → dist/esm}/package.json +0 -0
  88. /package/{esm → dist/esm}/parser.js +0 -0
  89. /package/{esm → dist/esm}/send-utils.js +0 -0
  90. /package/{esm → dist/esm}/send.js +0 -0
  91. /package/{esm → dist/esm}/signer-utils.js +0 -0
  92. /package/{esm → dist/esm}/signer.js +0 -0
  93. /package/{esm → dist/esm}/structured.js +0 -0
  94. /package/{esm → dist/esm}/test.js +0 -0
  95. /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
+ ]
@@ -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
+ }