@marcuspuchalla/nachos 0.1.0

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 (100) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +674 -0
  3. package/README.md +345 -0
  4. package/dist/chunk-2FUTHZQQ.cjs +755 -0
  5. package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
  6. package/dist/chunk-2HBCILJS.cjs +2034 -0
  7. package/dist/chunk-2HBCILJS.cjs.map +1 -0
  8. package/dist/chunk-7CFYWHS6.js +742 -0
  9. package/dist/chunk-7CFYWHS6.js.map +1 -0
  10. package/dist/chunk-PD72MVTX.cjs +160 -0
  11. package/dist/chunk-PD72MVTX.cjs.map +1 -0
  12. package/dist/chunk-ZDZ2B5PE.js +149 -0
  13. package/dist/chunk-ZDZ2B5PE.js.map +1 -0
  14. package/dist/chunk-ZRPJUEIZ.js +2020 -0
  15. package/dist/chunk-ZRPJUEIZ.js.map +1 -0
  16. package/dist/encoder/index.cjs +57 -0
  17. package/dist/encoder/index.cjs.map +1 -0
  18. package/dist/encoder/index.d.cts +72 -0
  19. package/dist/encoder/index.d.ts +72 -0
  20. package/dist/encoder/index.js +4 -0
  21. package/dist/encoder/index.js.map +1 -0
  22. package/dist/index.cjs +606 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +494 -0
  25. package/dist/index.d.ts +494 -0
  26. package/dist/index.js +523 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/metafile-cjs.json +1 -0
  29. package/dist/metafile-esm.json +1 -0
  30. package/dist/parser/index.cjs +85 -0
  31. package/dist/parser/index.cjs.map +1 -0
  32. package/dist/parser/index.d.cts +72 -0
  33. package/dist/parser/index.d.ts +72 -0
  34. package/dist/parser/index.js +4 -0
  35. package/dist/parser/index.js.map +1 -0
  36. package/dist/types-DvNlfbKB.d.cts +301 -0
  37. package/dist/types-DvNlfbKB.d.ts +301 -0
  38. package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
  39. package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
  40. package/dist/useCborTag-B_iaShG6.d.ts +142 -0
  41. package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
  42. package/package.json +102 -0
  43. package/src/__tests__/public-api.test.ts +326 -0
  44. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
  45. package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
  46. package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
  47. package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
  48. package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
  49. package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
  50. package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
  51. package/src/encoder/composables/useCborEncoder.ts +203 -0
  52. package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
  53. package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
  54. package/src/encoder/composables/useCborStringEncoder.ts +266 -0
  55. package/src/encoder/composables/useCborTagEncoder.ts +158 -0
  56. package/src/encoder/index.ts +35 -0
  57. package/src/encoder/types.ts +88 -0
  58. package/src/encoder/utils.ts +80 -0
  59. package/src/index.ts +434 -0
  60. package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
  61. package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
  62. package/src/parser/__tests__/cbor-collection.test.ts +369 -0
  63. package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
  64. package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
  65. package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
  66. package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
  67. package/src/parser/__tests__/cbor-float.test.ts +502 -0
  68. package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
  69. package/src/parser/__tests__/cbor-integer.test.ts +200 -0
  70. package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
  71. package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
  72. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
  73. package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
  74. package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
  75. package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
  76. package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
  77. package/src/parser/__tests__/cbor-string.test.ts +224 -0
  78. package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
  79. package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
  80. package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
  81. package/src/parser/__tests__/cbor-tag.test.ts +684 -0
  82. package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
  83. package/src/parser/__tests__/pathBuilder.test.ts +256 -0
  84. package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
  85. package/src/parser/__tests__/security-limits.test.ts +248 -0
  86. package/src/parser/__tests__/utils-errors.test.ts +127 -0
  87. package/src/parser/composables/useCborCollection.ts +509 -0
  88. package/src/parser/composables/useCborDiagnostic.ts +381 -0
  89. package/src/parser/composables/useCborFloat.ts +256 -0
  90. package/src/parser/composables/useCborInteger.ts +114 -0
  91. package/src/parser/composables/useCborParser.ts +951 -0
  92. package/src/parser/composables/useCborString.ts +330 -0
  93. package/src/parser/composables/useCborStringTypes.ts +129 -0
  94. package/src/parser/composables/useCborTag.ts +739 -0
  95. package/src/parser/index.ts +56 -0
  96. package/src/parser/types.ts +371 -0
  97. package/src/parser/utils/pathBuilder.ts +259 -0
  98. package/src/parser/utils.ts +398 -0
  99. package/src/utils/__tests__/logger.test.ts +186 -0
  100. package/src/utils/logger.ts +96 -0
@@ -0,0 +1,333 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { useCborDiagnostic } from '../composables/useCborDiagnostic'
3
+
4
+ describe('useCborDiagnostic', () => {
5
+ const { toDiagnostic, fromDiagnostic } = useCborDiagnostic()
6
+
7
+ describe('primitive values', () => {
8
+ it('should format integers', () => {
9
+ expect(toDiagnostic(0)).toBe('0')
10
+ expect(toDiagnostic(1)).toBe('1')
11
+ expect(toDiagnostic(23)).toBe('23')
12
+ expect(toDiagnostic(100)).toBe('100')
13
+ expect(toDiagnostic(1000000)).toBe('1000000')
14
+ expect(toDiagnostic(-1)).toBe('-1')
15
+ expect(toDiagnostic(-100)).toBe('-100')
16
+ })
17
+
18
+ it('should format BigInt', () => {
19
+ expect(toDiagnostic(BigInt(100))).toBe('100')
20
+ expect(toDiagnostic(BigInt('18446744073709551615'))).toBe('18446744073709551615')
21
+ expect(toDiagnostic(BigInt(-100))).toBe('-100')
22
+ })
23
+
24
+ it('should format floats', () => {
25
+ expect(toDiagnostic(1.5)).toBe('1.5')
26
+ expect(toDiagnostic(100000.0)).toBe('100000')
27
+ expect(toDiagnostic(3.14159)).toBe('3.14159')
28
+ })
29
+
30
+ it('should format special float values', () => {
31
+ expect(toDiagnostic(NaN)).toBe('NaN')
32
+ expect(toDiagnostic(Infinity)).toBe('Infinity')
33
+ expect(toDiagnostic(-Infinity)).toBe('-Infinity')
34
+ expect(toDiagnostic(-0)).toBe('-0.0')
35
+ })
36
+
37
+ it('should format booleans', () => {
38
+ expect(toDiagnostic(true)).toBe('true')
39
+ expect(toDiagnostic(false)).toBe('false')
40
+ })
41
+
42
+ it('should format null and undefined', () => {
43
+ expect(toDiagnostic(null)).toBe('null')
44
+ expect(toDiagnostic(undefined)).toBe('undefined')
45
+ })
46
+ })
47
+
48
+ describe('strings', () => {
49
+ it('should format simple strings', () => {
50
+ expect(toDiagnostic('')).toBe('""')
51
+ expect(toDiagnostic('a')).toBe('"a"')
52
+ expect(toDiagnostic('IETF')).toBe('"IETF"')
53
+ expect(toDiagnostic('hello world')).toBe('"hello world"')
54
+ })
55
+
56
+ it('should escape special characters', () => {
57
+ expect(toDiagnostic('line1\nline2')).toBe('"line1\\nline2"')
58
+ expect(toDiagnostic('tab\there')).toBe('"tab\\there"')
59
+ expect(toDiagnostic('quote"here')).toBe('"quote\\"here"')
60
+ expect(toDiagnostic('back\\slash')).toBe('"back\\\\slash"')
61
+ })
62
+
63
+ it('should escape control characters', () => {
64
+ expect(toDiagnostic('\x00')).toBe('"\\u0000"')
65
+ expect(toDiagnostic('\x1f')).toBe('"\\u001f"')
66
+ })
67
+ })
68
+
69
+ describe('byte strings', () => {
70
+ it('should format empty byte string', () => {
71
+ expect(toDiagnostic(new Uint8Array([]))).toBe("h''")
72
+ })
73
+
74
+ it('should format byte strings', () => {
75
+ expect(toDiagnostic(new Uint8Array([1, 2, 3, 4]))).toBe("h'01020304'")
76
+ expect(toDiagnostic(new Uint8Array([0xde, 0xad, 0xbe, 0xef]))).toBe("h'deadbeef'")
77
+ })
78
+
79
+ it('should format single byte', () => {
80
+ expect(toDiagnostic(new Uint8Array([0xff]))).toBe("h'ff'")
81
+ expect(toDiagnostic(new Uint8Array([0x00]))).toBe("h'00'")
82
+ })
83
+ })
84
+
85
+ describe('arrays', () => {
86
+ it('should format empty array', () => {
87
+ expect(toDiagnostic([])).toBe('[]')
88
+ })
89
+
90
+ it('should format simple arrays', () => {
91
+ expect(toDiagnostic([1, 2, 3])).toBe('[1, 2, 3]')
92
+ expect(toDiagnostic(['a', 'b'])).toBe('["a", "b"]')
93
+ })
94
+
95
+ it('should format nested arrays', () => {
96
+ expect(toDiagnostic([1, [2, 3]])).toBe('[1, [2, 3]]')
97
+ expect(toDiagnostic([[1, 2], [3, 4]])).toBe('[[1, 2], [3, 4]]')
98
+ })
99
+
100
+ it('should format indefinite arrays', () => {
101
+ expect(toDiagnostic([1, 2, 3], { indefinite: true })).toBe('[_ 1, 2, 3]')
102
+ expect(toDiagnostic([], { indefinite: true })).toBe('[_ ]')
103
+ })
104
+
105
+ it('should format mixed type arrays', () => {
106
+ expect(toDiagnostic([1, 'hello', true, null])).toBe('[1, "hello", true, null]')
107
+ })
108
+ })
109
+
110
+ describe('maps and objects', () => {
111
+ it('should format empty objects', () => {
112
+ expect(toDiagnostic({})).toBe('{}')
113
+ })
114
+
115
+ it('should format simple objects', () => {
116
+ expect(toDiagnostic({ a: 1 })).toBe('{"a": 1}')
117
+ expect(toDiagnostic({ a: 1, b: 2 })).toBe('{"a": 1, "b": 2}')
118
+ })
119
+
120
+ it('should format nested objects', () => {
121
+ expect(toDiagnostic({ a: { b: 1 } })).toBe('{"a": {"b": 1}}')
122
+ })
123
+
124
+ it('should format indefinite maps', () => {
125
+ expect(toDiagnostic({ a: 1 }, { indefinite: true })).toBe('{_ "a": 1}')
126
+ })
127
+
128
+ it('should format Maps with non-string keys', () => {
129
+ const map = new Map<any, any>([[1, 'one'], [2, 'two']])
130
+ expect(toDiagnostic(map)).toBe('{1: "one", 2: "two"}')
131
+ })
132
+
133
+ it('should format empty Maps', () => {
134
+ expect(toDiagnostic(new Map())).toBe('{}')
135
+ })
136
+ })
137
+
138
+ describe('tagged values', () => {
139
+ it('should format simple tags', () => {
140
+ expect(toDiagnostic({ tag: 0, value: '2024-01-01T00:00:00Z' }))
141
+ .toBe('0("2024-01-01T00:00:00Z")')
142
+ })
143
+
144
+ it('should format epoch timestamp tag', () => {
145
+ expect(toDiagnostic({ tag: 1, value: 1363896240 }))
146
+ .toBe('1(1363896240)')
147
+ })
148
+
149
+ it('should format bignum tags', () => {
150
+ expect(toDiagnostic({ tag: 2, value: new Uint8Array([1, 0, 0, 0, 0, 0, 0, 0, 0]) }))
151
+ .toBe("2(h'010000000000000000')")
152
+ })
153
+
154
+ it('should format self-describe CBOR tag', () => {
155
+ expect(toDiagnostic({ tag: 55799, value: [1, 2] }))
156
+ .toBe('55799([1, 2])')
157
+ })
158
+
159
+ it('should format Plutus constructor tags', () => {
160
+ expect(toDiagnostic({ tag: 121, value: [1, 2] }))
161
+ .toBe('121([1, 2])')
162
+ expect(toDiagnostic({ tag: 122, value: [] }))
163
+ .toBe('122([])')
164
+ })
165
+
166
+ it('should format nested tags', () => {
167
+ expect(toDiagnostic({ tag: 1, value: { tag: 0, value: 'test' } }))
168
+ .toBe('1(0("test"))')
169
+ })
170
+ })
171
+
172
+ describe('pretty printing', () => {
173
+ it('should pretty print arrays', () => {
174
+ const result = toDiagnostic([1, 2, 3], { pretty: true })
175
+ expect(result).toBe('[\n 1,\n 2,\n 3\n]')
176
+ })
177
+
178
+ it('should pretty print objects', () => {
179
+ const result = toDiagnostic({ a: 1, b: 2 }, { pretty: true })
180
+ expect(result).toBe('{\n "a": 1,\n "b": 2\n}')
181
+ })
182
+
183
+ it('should pretty print nested structures', () => {
184
+ const result = toDiagnostic({ arr: [1, 2] }, { pretty: true })
185
+ expect(result).toContain('{\n')
186
+ expect(result).toContain('"arr":')
187
+ expect(result).toContain('[')
188
+ })
189
+
190
+ it('should use custom indent', () => {
191
+ const result = toDiagnostic([1, 2], { pretty: true, indent: ' ' })
192
+ expect(result).toBe('[\n 1,\n 2\n]')
193
+ })
194
+ })
195
+
196
+ describe('fromDiagnostic (basic parsing)', () => {
197
+ it('should parse null and undefined', () => {
198
+ expect(fromDiagnostic('null')).toBe(null)
199
+ expect(fromDiagnostic('undefined')).toBe(undefined)
200
+ })
201
+
202
+ it('should parse booleans', () => {
203
+ expect(fromDiagnostic('true')).toBe(true)
204
+ expect(fromDiagnostic('false')).toBe(false)
205
+ })
206
+
207
+ it('should parse special floats', () => {
208
+ expect(fromDiagnostic('NaN')).toBeNaN()
209
+ expect(fromDiagnostic('Infinity')).toBe(Infinity)
210
+ expect(fromDiagnostic('-Infinity')).toBe(-Infinity)
211
+ })
212
+
213
+ it('should parse numbers', () => {
214
+ expect(fromDiagnostic('100')).toBe(100)
215
+ expect(fromDiagnostic('-50')).toBe(-50)
216
+ expect(fromDiagnostic('3.14')).toBe(3.14)
217
+ })
218
+
219
+ it('should parse byte strings', () => {
220
+ const result = fromDiagnostic("h'0102'")
221
+ expect(result).toBeInstanceOf(Uint8Array)
222
+ expect(Array.from(result as Uint8Array)).toEqual([1, 2])
223
+ })
224
+
225
+ it('should parse empty byte string', () => {
226
+ const result = fromDiagnostic("h''")
227
+ expect(result).toBeInstanceOf(Uint8Array)
228
+ expect((result as Uint8Array).length).toBe(0)
229
+ })
230
+
231
+ it('should parse strings', () => {
232
+ expect(fromDiagnostic('"hello"')).toBe('hello')
233
+ expect(fromDiagnostic('""')).toBe('')
234
+ })
235
+ })
236
+
237
+ describe('RFC 8949 Appendix A examples', () => {
238
+ // Examples from RFC 8949 Appendix A
239
+
240
+ it('should format RFC examples - integers', () => {
241
+ expect(toDiagnostic(0)).toBe('0')
242
+ expect(toDiagnostic(23)).toBe('23')
243
+ expect(toDiagnostic(24)).toBe('24')
244
+ expect(toDiagnostic(100)).toBe('100')
245
+ expect(toDiagnostic(1000)).toBe('1000')
246
+ expect(toDiagnostic(1000000)).toBe('1000000')
247
+ })
248
+
249
+ it('should format RFC examples - negative integers', () => {
250
+ expect(toDiagnostic(-1)).toBe('-1')
251
+ expect(toDiagnostic(-10)).toBe('-10')
252
+ expect(toDiagnostic(-100)).toBe('-100')
253
+ expect(toDiagnostic(-1000)).toBe('-1000')
254
+ })
255
+
256
+ it('should format RFC examples - floats', () => {
257
+ expect(toDiagnostic(0.0)).toBe('0')
258
+ expect(toDiagnostic(1.0)).toBe('1')
259
+ expect(toDiagnostic(1.5)).toBe('1.5')
260
+ })
261
+
262
+ it('should format RFC examples - strings', () => {
263
+ expect(toDiagnostic('')).toBe('""')
264
+ expect(toDiagnostic('a')).toBe('"a"')
265
+ expect(toDiagnostic('IETF')).toBe('"IETF"')
266
+ })
267
+
268
+ it('should format RFC examples - arrays', () => {
269
+ expect(toDiagnostic([])).toBe('[]')
270
+ expect(toDiagnostic([1, 2, 3])).toBe('[1, 2, 3]')
271
+ expect(toDiagnostic([1, [2, 3], [4, 5]])).toBe('[1, [2, 3], [4, 5]]')
272
+ })
273
+
274
+ it('should format RFC examples - maps', () => {
275
+ expect(toDiagnostic({})).toBe('{}')
276
+ expect(toDiagnostic({ a: 1, b: [2, 3] })).toBe('{"a": 1, "b": [2, 3]}')
277
+ })
278
+ })
279
+
280
+ describe('Cardano-specific examples', () => {
281
+ it('should format UTXO structure', () => {
282
+ const utxo = [
283
+ [
284
+ new Uint8Array([0x48, 0xbd, 0x01, 0xd5]), // txHash (truncated)
285
+ 0 // index
286
+ ]
287
+ ]
288
+ const result = toDiagnostic(utxo)
289
+ expect(result).toBe("[[h'48bd01d5', 0]]")
290
+ })
291
+
292
+ it('should format Plutus constructor', () => {
293
+ const constructor = { tag: 121, value: [1, 2] }
294
+ expect(toDiagnostic(constructor)).toBe('121([1, 2])')
295
+ })
296
+
297
+ it('should format transaction metadata map', () => {
298
+ const metadata = new Map([
299
+ [674, { msg: ['Hello', 'World'] }]
300
+ ])
301
+ const result = toDiagnostic(metadata)
302
+ expect(result).toContain('674:')
303
+ expect(result).toContain('"msg"')
304
+ })
305
+ })
306
+
307
+ describe('edge cases', () => {
308
+ it('should handle depth limit', () => {
309
+ const deepNested = { a: { b: { c: { d: 1 } } } }
310
+ const result = toDiagnostic(deepNested, { maxDepth: 2 })
311
+ expect(result).toContain('...')
312
+ })
313
+
314
+ it('should handle Sets', () => {
315
+ const set = new Set([1, 2, 3])
316
+ expect(toDiagnostic(set)).toBe('[1, 2, 3]')
317
+ })
318
+
319
+ it('should handle mixed content arrays', () => {
320
+ const mixed = [
321
+ 1,
322
+ 'text',
323
+ new Uint8Array([0xff]),
324
+ { key: 'value' },
325
+ [1, 2],
326
+ true,
327
+ null
328
+ ]
329
+ const result = toDiagnostic(mixed)
330
+ expect(result).toBe('[1, "text", h\'ff\', {"key": "value"}, [1, 2], true, null]')
331
+ })
332
+ })
333
+ })
@@ -0,0 +1,235 @@
1
+ /**
2
+ * CBOR Duplicate Map Key Detection Tests
3
+ * Tests RFC 8949 Section 5.6 requirement for strict mode
4
+ *
5
+ * RFC 8949: "A map that has duplicate keys may be well-formed, but it is not valid,
6
+ * and duplicate keys are prohibited by CBOR decoders that are using strict mode."
7
+ *
8
+ * Tests cover:
9
+ * - Duplicate key detection with different modes ('allow', 'warn', 'reject')
10
+ * - Various key types (strings, integers, byte strings)
11
+ * - Nested maps with duplicates
12
+ * - Real-world Cardano scenarios
13
+ */
14
+
15
+ import { describe, it, expect, vi } from 'vitest'
16
+ import { useCborParser } from '../composables/useCborParser'
17
+
18
+ describe('useCborParser - Duplicate Map Key Detection', () => {
19
+ describe('Allow Mode (default)', () => {
20
+ it('should allow duplicate string keys by default', () => {
21
+ const { parseWithSourceMap } = useCborParser()
22
+
23
+ // Map with duplicate key "a": {"a": 1, "a": 2}
24
+ // a2 6161 01 6161 02
25
+ const duplicateHex = 'a2616101616102'
26
+
27
+ const result = parseWithSourceMap(duplicateHex)
28
+
29
+ expect(result.value).toBeDefined()
30
+ // Last value wins in JavaScript objects
31
+ expect(result.value.get("a")).toBe(2)
32
+ })
33
+
34
+ it('should allow duplicate integer keys by default', () => {
35
+ const { parseWithSourceMap } = useCborParser()
36
+
37
+ // Map with duplicate key 1: {1: 10, 1: 20}
38
+ // a2 01 0a 01 14
39
+ const duplicateHex = 'a2010a0114'
40
+
41
+ const result = parseWithSourceMap(duplicateHex)
42
+
43
+ expect(result.value).toBeDefined()
44
+ expect(result.value.get(1)).toBe(20)
45
+ })
46
+ })
47
+
48
+ describe('Warn Mode', () => {
49
+ it('should warn but not throw on duplicate keys', () => {
50
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
51
+
52
+ const { parseWithSourceMap } = useCborParser()
53
+
54
+ // Map: {"a": 1, "a": 2}
55
+ const duplicateHex = 'a2616101616102'
56
+
57
+ const result = parseWithSourceMap(duplicateHex, {
58
+ dupMapKeyMode: 'warn'
59
+ })
60
+
61
+ expect(result.value).toBeDefined()
62
+ expect(consoleWarnSpy).toHaveBeenCalled()
63
+ expect(consoleWarnSpy.mock.calls[0][0]).toContain('Duplicate map key')
64
+
65
+ consoleWarnSpy.mockRestore()
66
+ })
67
+
68
+ it('should warn multiple times for multiple duplicates', () => {
69
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
70
+
71
+ const { parseWithSourceMap } = useCborParser()
72
+
73
+ // Map: {"a": 1, "a": 2, "a": 3}
74
+ // a3 6161 01 6161 02 6161 03
75
+ const duplicateHex = 'a3616101616102616103'
76
+
77
+ parseWithSourceMap(duplicateHex, {
78
+ dupMapKeyMode: 'warn'
79
+ })
80
+
81
+ // Should warn twice (second and third occurrence)
82
+ expect(consoleWarnSpy).toHaveBeenCalledTimes(2)
83
+
84
+ consoleWarnSpy.mockRestore()
85
+ })
86
+ })
87
+
88
+ describe('Reject Mode (strict)', () => {
89
+ it('should throw error on duplicate string keys', () => {
90
+ const { parseWithSourceMap } = useCborParser()
91
+
92
+ // Map: {"a": 1, "a": 2}
93
+ const duplicateHex = 'a2616101616102'
94
+
95
+ expect(() => {
96
+ parseWithSourceMap(duplicateHex, {
97
+ dupMapKeyMode: 'reject'
98
+ })
99
+ }).toThrow(/Duplicate map key/)
100
+ })
101
+
102
+ it('should throw error on duplicate integer keys', () => {
103
+ const { parseWithSourceMap } = useCborParser()
104
+
105
+ // Map: {1: 10, 1: 20}
106
+ const duplicateHex = 'a2010a0114'
107
+
108
+ expect(() => {
109
+ parseWithSourceMap(duplicateHex, {
110
+ dupMapKeyMode: 'reject'
111
+ })
112
+ }).toThrow(/Duplicate map key/)
113
+ })
114
+
115
+ it('should throw on first duplicate encountered', () => {
116
+ const { parseWithSourceMap } = useCborParser()
117
+
118
+ // Map: {"a": 1, "a": 2, "b": 3, "b": 4}
119
+ // Should fail at second "a"
120
+ const duplicateHex = 'a4616101616102616203616204'
121
+
122
+ expect(() => {
123
+ parseWithSourceMap(duplicateHex, {
124
+ dupMapKeyMode: 'reject'
125
+ })
126
+ }).toThrow(/Duplicate map key/)
127
+ })
128
+ })
129
+
130
+ describe('Different Key Types', () => {
131
+ it('should NOT flag different keys of same type', () => {
132
+ const { parseWithSourceMap } = useCborParser()
133
+
134
+ // Map: {"a": 1, "b": 2} - different keys, no error
135
+ // a2 6161 01 6162 02
136
+ const uniqueHex = 'a2616101616202'
137
+
138
+ const result = parseWithSourceMap(uniqueHex, {
139
+ dupMapKeyMode: 'reject'
140
+ })
141
+
142
+ expect(result.value).toBeDefined()
143
+ expect(result.value).toEqual(new Map([['a', 1], ['b', 2]]))
144
+ })
145
+
146
+ it('should handle byte string keys correctly', () => {
147
+ const { parseWithSourceMap } = useCborParser()
148
+
149
+ // Map with duplicate byte string key
150
+ // {h'01': 1, h'01': 2}
151
+ // a2 4101 01 4101 02
152
+ const duplicateHex = 'a2410101410102'
153
+
154
+ expect(() => {
155
+ parseWithSourceMap(duplicateHex, {
156
+ dupMapKeyMode: 'reject'
157
+ })
158
+ }).toThrow(/Duplicate map key/)
159
+ })
160
+ })
161
+
162
+ describe('Nested Maps', () => {
163
+ it('should detect duplicates in nested maps', () => {
164
+ const { parseWithSourceMap } = useCborParser()
165
+
166
+ // Map: {"outer": {"inner": 1, "inner": 2}}
167
+ // a1 656f75746572 a2 65696e6e6572 01 65696e6e6572 02
168
+ const duplicateHex = 'a1656f75746572a26569' + '6e6e65720165696e6e657202'
169
+
170
+ expect(() => {
171
+ parseWithSourceMap(duplicateHex, {
172
+ dupMapKeyMode: 'reject'
173
+ })
174
+ }).toThrow(/Duplicate map key/)
175
+ })
176
+
177
+ it('should allow same keys in different nested maps', () => {
178
+ const { parseWithSourceMap } = useCborParser()
179
+
180
+ // Map: {"map1": {"a": 1}, "map2": {"a": 2}}
181
+ // Same key "a" in different maps - OK
182
+ // a2 646d617031 a1616101 646d617032 a1616102
183
+ const validHex = 'a2646d617031a1616101646d617032a1616102'
184
+
185
+ const result = parseWithSourceMap(validHex, {
186
+ dupMapKeyMode: 'reject'
187
+ })
188
+
189
+ expect(result.value).toBeDefined()
190
+ })
191
+ })
192
+
193
+ describe('Edge Cases', () => {
194
+ it('should handle empty maps', () => {
195
+ const { parseWithSourceMap } = useCborParser()
196
+
197
+ // Empty map: {}
198
+ const emptyHex = 'a0'
199
+
200
+ const result = parseWithSourceMap(emptyHex, {
201
+ dupMapKeyMode: 'reject'
202
+ })
203
+
204
+ expect(result.value).toEqual(new Map())
205
+ })
206
+
207
+ it('should handle single-entry maps', () => {
208
+ const { parseWithSourceMap } = useCborParser()
209
+
210
+ // Map: {"a": 1}
211
+ const singleHex = 'a1616101'
212
+
213
+ const result = parseWithSourceMap(singleHex, {
214
+ dupMapKeyMode: 'reject'
215
+ })
216
+
217
+ expect(result.value).toEqual(new Map([['a', 1]]))
218
+ })
219
+
220
+ it('should handle large maps without duplicates', () => {
221
+ const { parseWithSourceMap } = useCborParser()
222
+
223
+ // Map with 10 unique keys
224
+ // {"a": 1, "b": 2, ..., "j": 10}
225
+ const largeHex = 'aa616101616202616303616404616505616606616707616808616909616a0a'
226
+
227
+ const result = parseWithSourceMap(largeHex, {
228
+ dupMapKeyMode: 'reject'
229
+ })
230
+
231
+ expect(result.value).toBeDefined()
232
+ expect(result.value.size).toBe(10)
233
+ })
234
+ })
235
+ })