@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.
- package/CHANGELOG.md +64 -0
- package/LICENSE +674 -0
- package/README.md +345 -0
- package/dist/chunk-2FUTHZQQ.cjs +755 -0
- package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
- package/dist/chunk-2HBCILJS.cjs +2034 -0
- package/dist/chunk-2HBCILJS.cjs.map +1 -0
- package/dist/chunk-7CFYWHS6.js +742 -0
- package/dist/chunk-7CFYWHS6.js.map +1 -0
- package/dist/chunk-PD72MVTX.cjs +160 -0
- package/dist/chunk-PD72MVTX.cjs.map +1 -0
- package/dist/chunk-ZDZ2B5PE.js +149 -0
- package/dist/chunk-ZDZ2B5PE.js.map +1 -0
- package/dist/chunk-ZRPJUEIZ.js +2020 -0
- package/dist/chunk-ZRPJUEIZ.js.map +1 -0
- package/dist/encoder/index.cjs +57 -0
- package/dist/encoder/index.cjs.map +1 -0
- package/dist/encoder/index.d.cts +72 -0
- package/dist/encoder/index.d.ts +72 -0
- package/dist/encoder/index.js +4 -0
- package/dist/encoder/index.js.map +1 -0
- package/dist/index.cjs +606 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +494 -0
- package/dist/index.d.ts +494 -0
- package/dist/index.js +523 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/parser/index.cjs +85 -0
- package/dist/parser/index.cjs.map +1 -0
- package/dist/parser/index.d.cts +72 -0
- package/dist/parser/index.d.ts +72 -0
- package/dist/parser/index.js +4 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/types-DvNlfbKB.d.cts +301 -0
- package/dist/types-DvNlfbKB.d.ts +301 -0
- package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
- package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
- package/dist/useCborTag-B_iaShG6.d.ts +142 -0
- package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
- package/package.json +102 -0
- package/src/__tests__/public-api.test.ts +326 -0
- package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
- package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
- package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
- package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
- package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
- package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
- package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
- package/src/encoder/composables/useCborEncoder.ts +203 -0
- package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
- package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
- package/src/encoder/composables/useCborStringEncoder.ts +266 -0
- package/src/encoder/composables/useCborTagEncoder.ts +158 -0
- package/src/encoder/index.ts +35 -0
- package/src/encoder/types.ts +88 -0
- package/src/encoder/utils.ts +80 -0
- package/src/index.ts +434 -0
- package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
- package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
- package/src/parser/__tests__/cbor-collection.test.ts +369 -0
- package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
- package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
- package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
- package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
- package/src/parser/__tests__/cbor-float.test.ts +502 -0
- package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
- package/src/parser/__tests__/cbor-integer.test.ts +200 -0
- package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
- package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
- package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
- package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
- package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
- package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
- package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
- package/src/parser/__tests__/cbor-string.test.ts +224 -0
- package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
- package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
- package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
- package/src/parser/__tests__/cbor-tag.test.ts +684 -0
- package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
- package/src/parser/__tests__/pathBuilder.test.ts +256 -0
- package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
- package/src/parser/__tests__/security-limits.test.ts +248 -0
- package/src/parser/__tests__/utils-errors.test.ts +127 -0
- package/src/parser/composables/useCborCollection.ts +509 -0
- package/src/parser/composables/useCborDiagnostic.ts +381 -0
- package/src/parser/composables/useCborFloat.ts +256 -0
- package/src/parser/composables/useCborInteger.ts +114 -0
- package/src/parser/composables/useCborParser.ts +951 -0
- package/src/parser/composables/useCborString.ts +330 -0
- package/src/parser/composables/useCborStringTypes.ts +129 -0
- package/src/parser/composables/useCborTag.ts +739 -0
- package/src/parser/index.ts +56 -0
- package/src/parser/types.ts +371 -0
- package/src/parser/utils/pathBuilder.ts +259 -0
- package/src/parser/utils.ts +398 -0
- package/src/utils/__tests__/logger.test.ts +186 -0
- package/src/utils/logger.ts +96 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extreme Edge Cases for 100% Coverage
|
|
3
|
+
* Tests for the last few uncovered lines
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { useCborInteger } from '../composables/useCborInteger'
|
|
8
|
+
import { useCborString } from '../composables/useCborString'
|
|
9
|
+
|
|
10
|
+
describe('Extreme Edge Cases', () => {
|
|
11
|
+
describe('useCborInteger - Line 81 coverage', () => {
|
|
12
|
+
it('should keep BigInt when converting negative BigInt that stays outside safe range', () => {
|
|
13
|
+
const { parseInteger } = useCborInteger()
|
|
14
|
+
|
|
15
|
+
// Test the else branch on line 83: when negValue as BigInt stays as BigInt
|
|
16
|
+
// We need a value where the rawValue is BigInt AND the result is BigInt
|
|
17
|
+
// -1 - rawValue where rawValue > MAX_SAFE_INTEGER
|
|
18
|
+
// This will keep it as BigInt
|
|
19
|
+
|
|
20
|
+
// Use a value just above MAX_SAFE_INTEGER
|
|
21
|
+
// MAX_SAFE_INTEGER is 9007199254740991 (0x001FFFFFFFFFFFFF)
|
|
22
|
+
// We want rawValue = 9007199254740992 (0x0020000000000000)
|
|
23
|
+
// Result: -1 - 9007199254740992 = -9007199254740993 (outside safe range)
|
|
24
|
+
|
|
25
|
+
const result = parseInteger('3b0020000000000000')
|
|
26
|
+
expect(typeof result.value).toBe('bigint')
|
|
27
|
+
expect(result.value).toBe(-9007199254740993n)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should convert large BigInt negative to number when it fits', () => {
|
|
31
|
+
const { parseInteger } = useCborInteger()
|
|
32
|
+
|
|
33
|
+
// Test line 80-81: when negValue >= MIN_SAFE_INTEGER and converts to Number
|
|
34
|
+
// MIN_SAFE_INTEGER = -9007199254740991
|
|
35
|
+
// We want: -1 - rawValue >= -9007199254740991
|
|
36
|
+
// So: rawValue <= 9007199254740990
|
|
37
|
+
|
|
38
|
+
// Use rawValue = 9007199254740990 (just at the boundary)
|
|
39
|
+
// -1 - 9007199254740990 = -9007199254740991 (exactly MIN_SAFE_INTEGER)
|
|
40
|
+
const result = parseInteger('3b001ffffffffffffe')
|
|
41
|
+
|
|
42
|
+
// Should be converted to Number
|
|
43
|
+
expect(typeof result.value).toBe('number')
|
|
44
|
+
expect(result.value).toBe(-9007199254740991)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('useCborString - Indefinite edge cases', () => {
|
|
49
|
+
it('should handle edge case with indefinite string chunks', () => {
|
|
50
|
+
const { parseByteString } = useCborString()
|
|
51
|
+
|
|
52
|
+
// Test indefinite byte string with empty chunks
|
|
53
|
+
// 5f (indefinite) + 40 (empty chunk) + 40 (empty chunk) + ff (break)
|
|
54
|
+
const buffer = new Uint8Array([0x5f, 0x40, 0x40, 0xff])
|
|
55
|
+
const result = parseByteString(buffer, 0)
|
|
56
|
+
|
|
57
|
+
// Parser returns structured object with chunk metadata for round-tripping
|
|
58
|
+
expect(result.value).toMatchObject({
|
|
59
|
+
type: 'cbor-byte-string',
|
|
60
|
+
bytes: new Uint8Array([]),
|
|
61
|
+
chunks: [new Uint8Array([]), new Uint8Array([])]
|
|
62
|
+
})
|
|
63
|
+
expect(result.bytesRead).toBe(4)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should handle indefinite text string with empty chunks', () => {
|
|
67
|
+
const { parseTextString } = useCborString()
|
|
68
|
+
|
|
69
|
+
// Test indefinite text string with empty chunks
|
|
70
|
+
// 7f (indefinite) + 60 (empty chunk) + 60 (empty chunk) + ff (break)
|
|
71
|
+
const buffer = new Uint8Array([0x7f, 0x60, 0x60, 0xff])
|
|
72
|
+
const result = parseTextString(buffer, 0)
|
|
73
|
+
|
|
74
|
+
// Parser returns structured object with chunk metadata for round-tripping
|
|
75
|
+
expect(result.value).toMatchObject({
|
|
76
|
+
type: 'cbor-text-string',
|
|
77
|
+
text: '',
|
|
78
|
+
chunks: ['', '']
|
|
79
|
+
})
|
|
80
|
+
expect(result.bytesRead).toBe(4)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('Length encoding edge cases', () => {
|
|
85
|
+
it('should use all length encoding paths', () => {
|
|
86
|
+
const { parseByteString } = useCborString()
|
|
87
|
+
|
|
88
|
+
// Direct encoding (0-23): 23 bytes
|
|
89
|
+
const buffer1 = new Uint8Array([0x57, ...new Array(23).fill(0)])
|
|
90
|
+
const result1 = parseByteString(buffer1, 0)
|
|
91
|
+
expect(result1.value.length).toBe(23)
|
|
92
|
+
|
|
93
|
+
// 1-byte length (AI 24): 100 bytes
|
|
94
|
+
const buffer2 = new Uint8Array([0x58, 100, ...new Array(100).fill(0)])
|
|
95
|
+
const result2 = parseByteString(buffer2, 0)
|
|
96
|
+
expect(result2.value.length).toBe(100)
|
|
97
|
+
|
|
98
|
+
// 2-byte length (AI 25): 256 bytes
|
|
99
|
+
const buffer3 = new Uint8Array([0x59, 0x01, 0x00, ...new Array(256).fill(0)])
|
|
100
|
+
const result3 = parseByteString(buffer3, 0)
|
|
101
|
+
expect(result3.value.length).toBe(256)
|
|
102
|
+
|
|
103
|
+
// 4-byte length (AI 26): 300 bytes
|
|
104
|
+
const buffer4 = new Uint8Array([0x5a, 0x00, 0x00, 0x01, 0x2c, ...new Array(300).fill(0)])
|
|
105
|
+
const result4 = parseByteString(buffer4, 0)
|
|
106
|
+
expect(result4.value.length).toBe(300)
|
|
107
|
+
|
|
108
|
+
// 8-byte length (AI 27): 500 bytes
|
|
109
|
+
const buffer5 = new Uint8Array([0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf4, ...new Array(500).fill(0)])
|
|
110
|
+
const result5 = parseByteString(buffer5, 0)
|
|
111
|
+
expect(result5.value.length).toBe(500)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('Boundary value testing', () => {
|
|
116
|
+
it('should handle maximum safe integer boundaries', () => {
|
|
117
|
+
const { parseInteger } = useCborInteger()
|
|
118
|
+
|
|
119
|
+
// Maximum safe positive integer: 2^53 - 1 = 9007199254740991
|
|
120
|
+
const result1 = parseInteger('1b001fffffffffffff')
|
|
121
|
+
expect(typeof result1.value).toBe('number')
|
|
122
|
+
expect(result1.value).toBe(9007199254740991)
|
|
123
|
+
|
|
124
|
+
// Just beyond max safe integer: 2^53 = 9007199254740992
|
|
125
|
+
const result2 = parseInteger('1b0020000000000000')
|
|
126
|
+
expect(typeof result2.value).toBe('bigint')
|
|
127
|
+
expect(result2.value).toBe(9007199254740992n)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should handle minimum safe integer boundary for negatives', () => {
|
|
131
|
+
const { parseInteger } = useCborInteger()
|
|
132
|
+
|
|
133
|
+
// Minimum safe negative integer: -(2^53 - 1) = -9007199254740991
|
|
134
|
+
// Encoding: -1 - N, so N = 9007199254740990
|
|
135
|
+
const result1 = parseInteger('3b001ffffffffffffe')
|
|
136
|
+
expect(typeof result1.value).toBe('number')
|
|
137
|
+
expect(result1.value).toBe(-9007199254740991)
|
|
138
|
+
|
|
139
|
+
// Just beyond min safe integer: -9007199254740992
|
|
140
|
+
// Encoding: -1 - N, so N = 9007199254740991
|
|
141
|
+
const result2 = parseInteger('3b001fffffffffffff')
|
|
142
|
+
expect(typeof result2.value).toBe('bigint')
|
|
143
|
+
expect(result2.value).toBe(-9007199254740992n)
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
})
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { PathBuilder } from '../utils/pathBuilder'
|
|
3
|
+
|
|
4
|
+
describe('PathBuilder', () => {
|
|
5
|
+
describe('basic path creation', () => {
|
|
6
|
+
it('should create root path', () => {
|
|
7
|
+
expect(PathBuilder.root()).toBe('')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('should create array index paths', () => {
|
|
11
|
+
expect(PathBuilder.arrayIndex('', 0)).toBe('[0]')
|
|
12
|
+
expect(PathBuilder.arrayIndex('', 1)).toBe('[1]')
|
|
13
|
+
expect(PathBuilder.arrayIndex('[0]', 2)).toBe('[0][2]')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should create nested array paths', () => {
|
|
17
|
+
const path1 = PathBuilder.arrayIndex('', 0)
|
|
18
|
+
const path2 = PathBuilder.arrayIndex(path1, 1)
|
|
19
|
+
const path3 = PathBuilder.arrayIndex(path2, 2)
|
|
20
|
+
|
|
21
|
+
expect(path1).toBe('[0]')
|
|
22
|
+
expect(path2).toBe('[0][1]')
|
|
23
|
+
expect(path3).toBe('[0][1][2]')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should create map key paths', () => {
|
|
27
|
+
expect(PathBuilder.mapKey('', 'amount')).toBe('.amount')
|
|
28
|
+
expect(PathBuilder.mapKey('[0]', 'txHash')).toBe('[0].txHash')
|
|
29
|
+
expect(PathBuilder.mapKey('[0].data', 'value')).toBe('[0].data.value')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should escape special characters in keys', () => {
|
|
33
|
+
expect(PathBuilder.mapKey('', 'key.with.dots')).toBe('.key\\.with\\.dots')
|
|
34
|
+
expect(PathBuilder.mapKey('', 'key[0]')).toBe('.key\\[0\\]')
|
|
35
|
+
expect(PathBuilder.mapKey('', 'back\\slash')).toBe('.back\\\\slash')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should create map key index paths', () => {
|
|
39
|
+
expect(PathBuilder.mapKeyIndex('', 0)).toBe('[#key:0]')
|
|
40
|
+
expect(PathBuilder.mapKeyIndex('[0]', 1)).toBe('[0][#key:1]')
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
describe('special markers', () => {
|
|
45
|
+
it('should create header paths', () => {
|
|
46
|
+
expect(PathBuilder.header('')).toBe('[#header]')
|
|
47
|
+
expect(PathBuilder.header('[0]')).toBe('[0][#header]')
|
|
48
|
+
expect(PathBuilder.header('[0].data')).toBe('[0].data[#header]')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should create content paths', () => {
|
|
52
|
+
expect(PathBuilder.content('')).toBe('[#content]')
|
|
53
|
+
expect(PathBuilder.content('[0]')).toBe('[0][#content]')
|
|
54
|
+
expect(PathBuilder.content('[0][1]')).toBe('[0][1][#content]')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should create tag value paths', () => {
|
|
58
|
+
expect(PathBuilder.tagValue('')).toBe('[#value]')
|
|
59
|
+
expect(PathBuilder.tagValue('[0]')).toBe('[0][#value]')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should normalize paths by removing markers', () => {
|
|
63
|
+
expect(PathBuilder.normalize('[0][#header]')).toBe('[0]')
|
|
64
|
+
expect(PathBuilder.normalize('[0][#content]')).toBe('[0]')
|
|
65
|
+
expect(PathBuilder.normalize('[0][#value]')).toBe('[0]')
|
|
66
|
+
expect(PathBuilder.normalize('[0].data[#header]')).toBe('[0].data')
|
|
67
|
+
expect(PathBuilder.normalize('[0]')).toBe('[0]') // no marker
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should check for header paths', () => {
|
|
71
|
+
expect(PathBuilder.isHeader('[0][#header]')).toBe(true)
|
|
72
|
+
expect(PathBuilder.isHeader('[0][#content]')).toBe(false)
|
|
73
|
+
expect(PathBuilder.isHeader('[0]')).toBe(false)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should check for content paths', () => {
|
|
77
|
+
expect(PathBuilder.isContent('[0][#content]')).toBe(true)
|
|
78
|
+
expect(PathBuilder.isContent('[0][#header]')).toBe(false)
|
|
79
|
+
expect(PathBuilder.isContent('[0]')).toBe(false)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should check for tag value paths', () => {
|
|
83
|
+
expect(PathBuilder.isTagValue('[0][#value]')).toBe(true)
|
|
84
|
+
expect(PathBuilder.isTagValue('[0][#header]')).toBe(false)
|
|
85
|
+
expect(PathBuilder.isTagValue('[0]')).toBe(false)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should check for any marker', () => {
|
|
89
|
+
expect(PathBuilder.hasMarker('[0][#header]')).toBe(true)
|
|
90
|
+
expect(PathBuilder.hasMarker('[0][#content]')).toBe(true)
|
|
91
|
+
expect(PathBuilder.hasMarker('[0][#value]')).toBe(true)
|
|
92
|
+
expect(PathBuilder.hasMarker('[0]')).toBe(false)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('path traversal', () => {
|
|
97
|
+
it('should get parent path', () => {
|
|
98
|
+
expect(PathBuilder.getParent('[0]')).toBe('')
|
|
99
|
+
expect(PathBuilder.getParent('[0][1]')).toBe('[0]')
|
|
100
|
+
expect(PathBuilder.getParent('[0].data')).toBe('[0]')
|
|
101
|
+
expect(PathBuilder.getParent('.field')).toBe('')
|
|
102
|
+
expect(PathBuilder.getParent('')).toBe(null)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should get parent path from marker paths', () => {
|
|
106
|
+
expect(PathBuilder.getParent('[0][#header]')).toBe('')
|
|
107
|
+
expect(PathBuilder.getParent('[0][1][#content]')).toBe('[0]')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should calculate path depth', () => {
|
|
111
|
+
expect(PathBuilder.getDepth('')).toBe(0)
|
|
112
|
+
expect(PathBuilder.getDepth('[0]')).toBe(1)
|
|
113
|
+
expect(PathBuilder.getDepth('[0][1]')).toBe(2)
|
|
114
|
+
expect(PathBuilder.getDepth('[0].data')).toBe(2)
|
|
115
|
+
expect(PathBuilder.getDepth('[0][1].field.nested')).toBe(4)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should calculate depth ignoring markers', () => {
|
|
119
|
+
expect(PathBuilder.getDepth('[0][#header]')).toBe(1)
|
|
120
|
+
expect(PathBuilder.getDepth('[0][1][#content]')).toBe(2)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
describe('path parsing and building', () => {
|
|
125
|
+
it('should parse simple paths', () => {
|
|
126
|
+
expect(PathBuilder.parse('[0]')).toEqual([
|
|
127
|
+
{ type: 'index', value: 0 }
|
|
128
|
+
])
|
|
129
|
+
|
|
130
|
+
expect(PathBuilder.parse('.field')).toEqual([
|
|
131
|
+
{ type: 'key', value: 'field' }
|
|
132
|
+
])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should parse nested paths', () => {
|
|
136
|
+
expect(PathBuilder.parse('[0][1]')).toEqual([
|
|
137
|
+
{ type: 'index', value: 0 },
|
|
138
|
+
{ type: 'index', value: 1 }
|
|
139
|
+
])
|
|
140
|
+
|
|
141
|
+
expect(PathBuilder.parse('[0].data.value')).toEqual([
|
|
142
|
+
{ type: 'index', value: 0 },
|
|
143
|
+
{ type: 'key', value: 'data' },
|
|
144
|
+
{ type: 'key', value: 'value' }
|
|
145
|
+
])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should parse paths with markers', () => {
|
|
149
|
+
expect(PathBuilder.parse('[0][#header]')).toEqual([
|
|
150
|
+
{ type: 'index', value: 0 },
|
|
151
|
+
{ type: 'marker', value: 'header' }
|
|
152
|
+
])
|
|
153
|
+
|
|
154
|
+
expect(PathBuilder.parse('[0][1][#content]')).toEqual([
|
|
155
|
+
{ type: 'index', value: 0 },
|
|
156
|
+
{ type: 'index', value: 1 },
|
|
157
|
+
{ type: 'marker', value: 'content' }
|
|
158
|
+
])
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should build paths from segments', () => {
|
|
162
|
+
expect(PathBuilder.build([
|
|
163
|
+
{ type: 'index', value: 0 }
|
|
164
|
+
])).toBe('[0]')
|
|
165
|
+
|
|
166
|
+
expect(PathBuilder.build([
|
|
167
|
+
{ type: 'index', value: 0 },
|
|
168
|
+
{ type: 'key', value: 'data' },
|
|
169
|
+
{ type: 'marker', value: 'header' }
|
|
170
|
+
])).toBe('[0].data[#header]')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should roundtrip parse and build', () => {
|
|
174
|
+
const paths = [
|
|
175
|
+
'[0]',
|
|
176
|
+
'[0][1]',
|
|
177
|
+
'.field',
|
|
178
|
+
'[0].data',
|
|
179
|
+
'[0][1].nested.value',
|
|
180
|
+
'[0][#header]',
|
|
181
|
+
'[0].data[#content]'
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
for (const path of paths) {
|
|
185
|
+
const segments = PathBuilder.parse(path)
|
|
186
|
+
const rebuilt = PathBuilder.build(segments)
|
|
187
|
+
expect(rebuilt).toBe(path)
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('path utilities', () => {
|
|
193
|
+
it('should join paths', () => {
|
|
194
|
+
expect(PathBuilder.join('', '[0]')).toBe('[0]')
|
|
195
|
+
expect(PathBuilder.join('[0]', '[1]')).toBe('[0][1]')
|
|
196
|
+
expect(PathBuilder.join('[0]', '.data')).toBe('[0].data')
|
|
197
|
+
expect(PathBuilder.join('', '', '[0]', '')).toBe('[0]')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should check descendant relationships', () => {
|
|
201
|
+
expect(PathBuilder.isDescendantOf('[0][1]', '[0]')).toBe(true)
|
|
202
|
+
expect(PathBuilder.isDescendantOf('[0].data', '[0]')).toBe(true)
|
|
203
|
+
expect(PathBuilder.isDescendantOf('[0][1][2]', '[0]')).toBe(true)
|
|
204
|
+
expect(PathBuilder.isDescendantOf('[1]', '[0]')).toBe(false)
|
|
205
|
+
expect(PathBuilder.isDescendantOf('[0]', '[0]')).toBe(false) // same path
|
|
206
|
+
expect(PathBuilder.isDescendantOf('[0]', '')).toBe(true) // root ancestor
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should handle marker paths in descendant check', () => {
|
|
210
|
+
expect(PathBuilder.isDescendantOf('[0][#header]', '')).toBe(true)
|
|
211
|
+
expect(PathBuilder.isDescendantOf('[0][1][#content]', '[0]')).toBe(true)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
describe('real-world CBOR paths', () => {
|
|
216
|
+
it('should handle Cardano UTXO paths', () => {
|
|
217
|
+
// Typical UTXO: [[txHash, index], amount]
|
|
218
|
+
const utxoPath = PathBuilder.arrayIndex('', 0)
|
|
219
|
+
const txHashPath = PathBuilder.arrayIndex(utxoPath, 0)
|
|
220
|
+
const indexPath = PathBuilder.arrayIndex(utxoPath, 1)
|
|
221
|
+
|
|
222
|
+
expect(utxoPath).toBe('[0]')
|
|
223
|
+
expect(txHashPath).toBe('[0][0]')
|
|
224
|
+
expect(indexPath).toBe('[0][1]')
|
|
225
|
+
|
|
226
|
+
// With header/content for byte string
|
|
227
|
+
const txHashHeader = PathBuilder.header(txHashPath)
|
|
228
|
+
const txHashContent = PathBuilder.content(txHashPath)
|
|
229
|
+
|
|
230
|
+
expect(txHashHeader).toBe('[0][0][#header]')
|
|
231
|
+
expect(txHashContent).toBe('[0][0][#content]')
|
|
232
|
+
expect(PathBuilder.normalize(txHashHeader)).toBe('[0][0]')
|
|
233
|
+
expect(PathBuilder.normalize(txHashContent)).toBe('[0][0]')
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('should handle map with string keys', () => {
|
|
237
|
+
// {amount: 1000000, fee: 200000}
|
|
238
|
+
const amountPath = PathBuilder.mapKey('', 'amount')
|
|
239
|
+
const feePath = PathBuilder.mapKey('', 'fee')
|
|
240
|
+
|
|
241
|
+
expect(amountPath).toBe('.amount')
|
|
242
|
+
expect(feePath).toBe('.fee')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('should handle nested Cardano transaction structure', () => {
|
|
246
|
+
// tx.inputs[0].txHash
|
|
247
|
+
const inputsPath = PathBuilder.mapKey('', 'inputs')
|
|
248
|
+
const firstInputPath = PathBuilder.arrayIndex(inputsPath, 0)
|
|
249
|
+
const txHashPath = PathBuilder.mapKey(firstInputPath, 'txHash')
|
|
250
|
+
|
|
251
|
+
expect(inputsPath).toBe('.inputs')
|
|
252
|
+
expect(firstInputPath).toBe('.inputs[0]')
|
|
253
|
+
expect(txHashPath).toBe('.inputs[0].txHash')
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
})
|