@suveren/gateway 0.29.1 → 0.30.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.
@@ -106,6 +106,146 @@ test('merge()', function (t) {
106
106
  s2t.end();
107
107
  });
108
108
 
109
+ st.test('with strictMerge, repeated conflicts append instead of nesting a new wrapper', function (s2t) {
110
+ var opts = { strictMerge: true };
111
+ var wrapped = utils.merge({ foo: 'bar' }, 'baz', opts);
112
+ s2t.deepEqual(wrapped, [{ foo: 'bar' }, 'baz'], 'first conflict wraps in an array');
113
+
114
+ var withObject = utils.merge(wrapped, { qux: 'quux' }, opts);
115
+ s2t.deepEqual(
116
+ withObject,
117
+ { 0: { foo: 'bar' }, 1: 'baz', qux: 'quux' },
118
+ 'merging an object converts the wrapper to an object'
119
+ );
120
+
121
+ var second = utils.merge(withObject, 'corge', opts);
122
+ s2t.deepEqual(
123
+ second,
124
+ { 0: { foo: 'bar' }, 1: 'baz', 2: 'corge', qux: 'quux' },
125
+ 'a second conflict appends at the next index'
126
+ );
127
+
128
+ var third = utils.merge(second, 'grault', opts);
129
+ s2t.deepEqual(
130
+ third,
131
+ { 0: { foo: 'bar' }, 1: 'baz', 2: 'corge', 3: 'grault', qux: 'quux' },
132
+ 'a third conflict appends again, so nesting depth stays constant'
133
+ );
134
+
135
+ s2t.end();
136
+ });
137
+
138
+ st.test('with strictMerge, a caller-supplied array is still wrapped, not appended to', function (s2t) {
139
+ var merged = utils.merge({ 0: 'a', 1: 'b' }, 'c', { strictMerge: true });
140
+ s2t.deepEqual(merged, [{ 0: 'a', 1: 'b' }, 'c'], 'an unmarked object with numeric keys is wrapped');
141
+ s2t.end();
142
+ });
143
+
144
+ st.test('with strictMerge, throws instead of appending a conflict past arrayLimit with throwOnLimitExceeded', function (s2t) {
145
+ var appendConflict = function (options) {
146
+ var wrapped = utils.merge({ foo: 'bar' }, 'baz', { strictMerge: true });
147
+ var container = utils.merge(wrapped, { qux: 'quux' }, { strictMerge: true });
148
+ return utils.merge(container, 'corge', options);
149
+ };
150
+
151
+ s2t['throws'](
152
+ function () { appendConflict({ strictMerge: true, arrayLimit: 1, throwOnLimitExceeded: true }); },
153
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
154
+ 'appending to a two-element container past arrayLimit throws'
155
+ );
156
+ s2t['throws'](
157
+ function () { appendConflict({ strictMerge: true, arrayLimit: 2, throwOnLimitExceeded: true }); },
158
+ new RangeError('Array limit exceeded. Only 2 elements allowed in an array.'),
159
+ 'uses the plural message when arrayLimit is not 1'
160
+ );
161
+ s2t.deepEqual(
162
+ appendConflict({ strictMerge: true, arrayLimit: 3, throwOnLimitExceeded: true }),
163
+ { 0: { foo: 'bar' }, 1: 'baz', 2: 'corge', qux: 'quux' },
164
+ 'appends while the container stays within arrayLimit'
165
+ );
166
+ s2t.deepEqual(
167
+ appendConflict({ strictMerge: true, arrayLimit: 1 }),
168
+ { 0: { foo: 'bar' }, 1: 'baz', 2: 'corge', qux: 'quux' },
169
+ 'still appends past arrayLimit without throwOnLimitExceeded, since the container is already an object'
170
+ );
171
+
172
+ s2t.end();
173
+ });
174
+
175
+ st.test('with strictMerge, a conflict appends after numeric keys already in its container', function (s2t) {
176
+ var opts = { strictMerge: true };
177
+
178
+ var container = utils.merge(utils.merge({ foo: 'bar' }, 'baz', opts), { qux: 'quux' }, opts);
179
+ var withIndex = utils.merge(container, { 2: 'keep' }, opts);
180
+ s2t.deepEqual(
181
+ utils.merge(withIndex, 'corge', opts),
182
+ { 0: { foo: 'bar' }, 1: 'baz', 2: 'keep', 3: 'corge', qux: 'quux' },
183
+ 'a numeric key merged into the container is not overwritten'
184
+ );
185
+
186
+ var sparse = [];
187
+ sparse[2] = 'keep';
188
+ var extended = utils.merge(utils.merge({ foo: 'bar' }, 'baz', opts), sparse, opts);
189
+ s2t.deepEqual(extended, [{ foo: 'bar' }, 'baz', 'keep'], 'an indexed value extends the container while it is an array');
190
+ var converted = utils.merge(extended, { qux: 'quux' }, opts);
191
+ s2t.deepEqual(
192
+ utils.merge(converted, 'corge', opts),
193
+ { 0: { foo: 'bar' }, 1: 'baz', 2: 'keep', 3: 'corge', qux: 'quux' },
194
+ 'converting the container to an object keeps its last index'
195
+ );
196
+
197
+ s2t.end();
198
+ });
199
+
200
+ st.test('enforces arrayLimit on the two-element array built for colliding values', function (s2t) {
201
+ s2t['throws'](
202
+ function () { utils.merge({ foo: 'bar' }, 'baz', { strictMerge: true, arrayLimit: 1, throwOnLimitExceeded: true }); },
203
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
204
+ 'a strictMerge conflict throws at arrayLimit 1'
205
+ );
206
+ s2t['throws'](
207
+ function () { utils.merge('a', 'b', { arrayLimit: 0, throwOnLimitExceeded: true }); },
208
+ new RangeError('Array limit exceeded. Only 0 elements allowed in an array.'),
209
+ 'two colliding primitives throw at arrayLimit 0'
210
+ );
211
+
212
+ var conflict = utils.merge({ foo: 'bar' }, 'baz', { strictMerge: true, arrayLimit: 1 });
213
+ s2t.ok(utils.isOverflow(conflict), 'without throwOnLimitExceeded, a strictMerge conflict converts to an overflow object');
214
+ s2t.deepEqual(conflict, { 0: { foo: 'bar' }, 1: 'baz' }, 'holding both values');
215
+
216
+ var pair = utils.merge('a', 'b', { arrayLimit: 1 });
217
+ s2t.ok(utils.isOverflow(pair), 'without throwOnLimitExceeded, two colliding primitives convert to an overflow object');
218
+ s2t.deepEqual(pair, { 0: 'a', 1: 'b' }, 'holding both values');
219
+
220
+ s2t.deepEqual(
221
+ utils.merge({ foo: 'bar' }, 'baz', { strictMerge: true, arrayLimit: 2, throwOnLimitExceeded: true }),
222
+ [{ foo: 'bar' }, 'baz'],
223
+ 'a strictMerge conflict within arrayLimit is still an array'
224
+ );
225
+ s2t.deepEqual(
226
+ utils.merge('a', 'b', { arrayLimit: 2, throwOnLimitExceeded: true }),
227
+ ['a', 'b'],
228
+ 'two colliding primitives within arrayLimit are still an array'
229
+ );
230
+
231
+ s2t.end();
232
+ });
233
+
234
+ st.test('seeds an overflow container index from the keys it already holds', function (s2t) {
235
+ var overflow = utils.combine(['a'], 'b', 0, false);
236
+ s2t.ok(utils.isOverflow(overflow), 'the source is an overflow object');
237
+
238
+ var merged = utils.merge({ 2: 'keep', z: 'zz' }, overflow);
239
+ s2t.ok(utils.isOverflow(merged), 'the result is marked as overflow');
240
+ s2t.deepEqual(
241
+ utils.merge(merged, 'c'),
242
+ { 0: 'a', 1: 'b', 2: 'keep', 3: 'c', z: 'zz' },
243
+ 'the next value lands after the highest numeric key the container already held'
244
+ );
245
+
246
+ s2t.end();
247
+ });
248
+
109
249
  st.test('merges overflow object into primitive', function (s2t) {
110
250
  // Create an overflow object via combine: 2 elements (indices 0-1) with limit 0
111
251
  var overflow = utils.combine(['a'], 'b', 0, false);
@@ -441,6 +581,50 @@ test('decode', function (t) {
441
581
  'decodes a string in iso-8859-1'
442
582
  );
443
583
 
584
+ t.test('strings with no percent sign', function (st) {
585
+ st.equal(utils.decode(''), '', 'empty string');
586
+ st.equal(utils.decode('abc'), 'abc', 'returns a plain string unchanged');
587
+ st.equal(utils.decode('abc', null, 'iso-8859-1'), 'abc', 'returns a plain string unchanged in iso-8859-1');
588
+ st.equal(utils.decode('a++b+'), 'a b ', 'decodes every + to a space');
589
+ st.equal(utils.decode('a++b+', null, 'iso-8859-1'), 'a b ', 'decodes every + to a space in iso-8859-1');
590
+ st.equal(utils.decode('\uD800x\uDC00'), '\uD800x\uDC00', 'leaves lone surrogates unchanged');
591
+ st.equal(utils.decode('😀+'), '😀 ', 'leaves surrogate pairs unchanged');
592
+ st.equal(utils.decode('a=b&c'), 'a=b&c', 'leaves delimiters unchanged');
593
+
594
+ st.end();
595
+ });
596
+
597
+ t.test('strings with a percent sign', function (st) {
598
+ st.equal(utils.decode('a+%20b'), 'a b', 'decodes both + and an escape');
599
+ st.equal(utils.decode('%E9+x', null, 'iso-8859-1'), 'é x', 'decodes both + and an escape in iso-8859-1');
600
+ st.equal(utils.decode('a+%'), 'a %', 'decodes + even when the escape is malformed');
601
+ st.equal(utils.decode('a+%E0%A4%A'), 'a %E0%A4%A', 'decodes + even when the escape is truncated');
602
+
603
+ st.end();
604
+ });
605
+
606
+ t.test('non-strings', function (st) {
607
+ forEach(v.nullPrimitives, function (nullish) {
608
+ st['throws'](
609
+ function () { utils.decode(nullish); },
610
+ TypeError,
611
+ inspect(nullish) + ' is not a string'
612
+ );
613
+ });
614
+ st['throws'](
615
+ function () { utils.decode(['a']); },
616
+ TypeError,
617
+ 'an array is not a string'
618
+ );
619
+
620
+ /* eslint no-new-wrappers: 0 */
621
+ st.equal(utils.decode(new String('ab')), 'ab', 'a boxed string decodes to a primitive');
622
+ st.equal(utils.decode(new String('a+b')), 'a b', 'a boxed string with + decodes to a primitive');
623
+ st.equal(utils.decode(new String('a%20b')), 'a b', 'a boxed string with an escape decodes to a primitive');
624
+
625
+ st.end();
626
+ });
627
+
444
628
  t.end();
445
629
  });
446
630
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suveren/gateway",
3
- "version": "0.29.1",
3
+ "version": "0.30.0",
4
4
  "description": "Suveren gateway — local agent gateway built in compliance with the Human Agency Protocol (HAP). Runs the UI, control plane, and MCP server in one Node process.",
5
5
  "type": "module",
6
6
  "main": "server.js",