@mapbox/mcp-server 0.0.2 → 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 (88) hide show
  1. package/dist/cjs/package.json +3 -0
  2. package/dist/esm/package.json +3 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +38 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/tools/MapboxApiBasedTool.d.ts +75 -0
  8. package/dist/tools/MapboxApiBasedTool.d.ts.map +1 -0
  9. package/dist/tools/MapboxApiBasedTool.js +78 -0
  10. package/dist/tools/MapboxApiBasedTool.js.map +1 -0
  11. package/dist/tools/category-search-tool/CategorySearchTool.d.ts +39 -0
  12. package/dist/tools/category-search-tool/CategorySearchTool.d.ts.map +1 -0
  13. package/dist/tools/category-search-tool/CategorySearchTool.js +638 -0
  14. package/dist/tools/category-search-tool/CategorySearchTool.js.map +1 -0
  15. package/dist/tools/category-search-tool/CategorySearchTool.test.d.ts +2 -0
  16. package/dist/tools/category-search-tool/CategorySearchTool.test.d.ts.map +1 -0
  17. package/dist/tools/category-search-tool/CategorySearchTool.test.js +289 -0
  18. package/dist/tools/category-search-tool/CategorySearchTool.test.js.map +1 -0
  19. package/dist/tools/directions-tool/DirectionsTool.d.ts +53 -0
  20. package/dist/tools/directions-tool/DirectionsTool.d.ts.map +1 -0
  21. package/dist/tools/directions-tool/DirectionsTool.js +405 -0
  22. package/dist/tools/directions-tool/DirectionsTool.js.map +1 -0
  23. package/dist/tools/directions-tool/DirectionsTool.test.d.ts +2 -0
  24. package/dist/tools/directions-tool/DirectionsTool.test.d.ts.map +1 -0
  25. package/dist/tools/directions-tool/DirectionsTool.test.js +867 -0
  26. package/dist/tools/directions-tool/DirectionsTool.test.js.map +1 -0
  27. package/dist/tools/forward-geocode-tool/ForwardGeocodeTool.d.ts +51 -0
  28. package/dist/tools/forward-geocode-tool/ForwardGeocodeTool.d.ts.map +1 -0
  29. package/dist/tools/forward-geocode-tool/ForwardGeocodeTool.js +182 -0
  30. package/dist/tools/forward-geocode-tool/ForwardGeocodeTool.js.map +1 -0
  31. package/dist/tools/forward-geocode-tool/ForwardGeocodeTool.test.d.ts +2 -0
  32. package/dist/tools/forward-geocode-tool/ForwardGeocodeTool.test.d.ts.map +1 -0
  33. package/dist/tools/forward-geocode-tool/ForwardGeocodeTool.test.js +335 -0
  34. package/dist/tools/forward-geocode-tool/ForwardGeocodeTool.test.js.map +1 -0
  35. package/dist/tools/isochrone-tool/IsochroneTool.d.ts +44 -0
  36. package/dist/tools/isochrone-tool/IsochroneTool.d.ts.map +1 -0
  37. package/dist/tools/isochrone-tool/IsochroneTool.js +108 -0
  38. package/dist/tools/isochrone-tool/IsochroneTool.js.map +1 -0
  39. package/dist/tools/isochrone-tool/IsochroneTool.test.d.ts +2 -0
  40. package/dist/tools/isochrone-tool/IsochroneTool.test.d.ts.map +1 -0
  41. package/dist/tools/isochrone-tool/IsochroneTool.test.js +110 -0
  42. package/dist/tools/isochrone-tool/IsochroneTool.test.js.map +1 -0
  43. package/dist/tools/matrix-tool/MatrixTool.d.ts +35 -0
  44. package/dist/tools/matrix-tool/MatrixTool.d.ts.map +1 -0
  45. package/dist/tools/matrix-tool/MatrixTool.js +195 -0
  46. package/dist/tools/matrix-tool/MatrixTool.js.map +1 -0
  47. package/dist/tools/matrix-tool/MatrixTool.test.d.ts +2 -0
  48. package/dist/tools/matrix-tool/MatrixTool.test.d.ts.map +1 -0
  49. package/dist/tools/matrix-tool/MatrixTool.test.js +803 -0
  50. package/dist/tools/matrix-tool/MatrixTool.test.js.map +1 -0
  51. package/dist/tools/poi-search-tool/PoiSearchTool.d.ts +54 -0
  52. package/dist/tools/poi-search-tool/PoiSearchTool.d.ts.map +1 -0
  53. package/dist/tools/poi-search-tool/PoiSearchTool.js +193 -0
  54. package/dist/tools/poi-search-tool/PoiSearchTool.js.map +1 -0
  55. package/dist/tools/poi-search-tool/PoiSearchTool.test.d.ts +2 -0
  56. package/dist/tools/poi-search-tool/PoiSearchTool.test.d.ts.map +1 -0
  57. package/dist/tools/poi-search-tool/PoiSearchTool.test.js +338 -0
  58. package/dist/tools/poi-search-tool/PoiSearchTool.test.js.map +1 -0
  59. package/dist/tools/reverse-geocode-tool/ReverseGeocodeTool.d.ts +42 -0
  60. package/dist/tools/reverse-geocode-tool/ReverseGeocodeTool.d.ts.map +1 -0
  61. package/dist/tools/reverse-geocode-tool/ReverseGeocodeTool.js +131 -0
  62. package/dist/tools/reverse-geocode-tool/ReverseGeocodeTool.js.map +1 -0
  63. package/dist/tools/reverse-geocode-tool/ReverseGeocodeTool.test.d.ts +2 -0
  64. package/dist/tools/reverse-geocode-tool/ReverseGeocodeTool.test.d.ts.map +1 -0
  65. package/dist/tools/reverse-geocode-tool/ReverseGeocodeTool.test.js +340 -0
  66. package/dist/tools/reverse-geocode-tool/ReverseGeocodeTool.test.js.map +1 -0
  67. package/dist/tools/static-map-image-tool/StaticMapImageTool.d.ts +148 -0
  68. package/dist/tools/static-map-image-tool/StaticMapImageTool.d.ts.map +1 -0
  69. package/dist/tools/static-map-image-tool/StaticMapImageTool.js +406 -0
  70. package/dist/tools/static-map-image-tool/StaticMapImageTool.js.map +1 -0
  71. package/dist/tools/static-map-image-tool/StaticMapImageTool.test.d.ts +2 -0
  72. package/dist/tools/static-map-image-tool/StaticMapImageTool.test.d.ts.map +1 -0
  73. package/dist/tools/static-map-image-tool/StaticMapImageTool.test.js +440 -0
  74. package/dist/tools/static-map-image-tool/StaticMapImageTool.test.js.map +1 -0
  75. package/dist/utils/requestUtils.d.ts +6 -0
  76. package/dist/utils/requestUtils.d.ts.map +1 -0
  77. package/dist/utils/requestUtils.js +29 -0
  78. package/dist/utils/requestUtils.js.map +1 -0
  79. package/dist/utils/requestUtils.test-helpers.d.ts +3 -0
  80. package/dist/utils/requestUtils.test-helpers.d.ts.map +1 -0
  81. package/dist/utils/requestUtils.test-helpers.js +28 -0
  82. package/dist/utils/requestUtils.test-helpers.js.map +1 -0
  83. package/dist/utils/versionUtils.d.ts +9 -0
  84. package/dist/utils/versionUtils.d.ts.map +1 -0
  85. package/dist/utils/versionUtils.js +25 -0
  86. package/dist/utils/versionUtils.js.map +1 -0
  87. package/dist/version.json +6 -0
  88. package/package.json +2 -2
@@ -0,0 +1,867 @@
1
+ process.env.MAPBOX_ACCESS_TOKEN = 'test-token';
2
+ import { cleanup } from '../../utils/requestUtils.js';
3
+ import { setupFetch, assertHeadersSent } from '../../utils/requestUtils.test-helpers.js';
4
+ import { DirectionsTool } from './DirectionsTool.js';
5
+ describe('DirectionsTool', () => {
6
+ afterEach(() => {
7
+ jest.restoreAllMocks();
8
+ cleanup();
9
+ });
10
+ it('sends custom header', async () => {
11
+ const mockFetch = setupFetch();
12
+ await new DirectionsTool().run({
13
+ coordinates: [
14
+ [-74.102094, 40.692815],
15
+ [-74.1022094, 40.792815]
16
+ ]
17
+ });
18
+ assertHeadersSent(mockFetch);
19
+ });
20
+ it('constructs correct URL with required parameters', async () => {
21
+ const mockFetch = setupFetch();
22
+ await new DirectionsTool().run({
23
+ coordinates: [
24
+ [-73.989, 40.733],
25
+ [-73.979, 40.743]
26
+ ]
27
+ });
28
+ const calledUrl = mockFetch.mock.calls[0][0];
29
+ expect(calledUrl).toContain('directions/v5/mapbox/driving-traffic');
30
+ expect(calledUrl).toContain('-73.989%2C40.733%3B-73.979%2C40.743');
31
+ expect(calledUrl).toContain('access_token=');
32
+ assertHeadersSent(mockFetch);
33
+ });
34
+ it('includes all optional parameters in URL', async () => {
35
+ const mockFetch = setupFetch();
36
+ await new DirectionsTool().run({
37
+ coordinates: [
38
+ [-122.42, 37.78],
39
+ [-122.4, 37.79],
40
+ [-122.39, 37.77]
41
+ ],
42
+ routing_profile: 'walking',
43
+ geometries: 'geojson',
44
+ alternatives: true,
45
+ annotations: ['distance', 'duration', 'speed'],
46
+ exclude: 'ferry'
47
+ });
48
+ const calledUrl = mockFetch.mock.calls[0][0];
49
+ expect(calledUrl).toContain('directions/v5/mapbox/walking');
50
+ expect(calledUrl).toContain('-122.42%2C37.78%3B-122.4%2C37.79%3B-122.39%2C37.77');
51
+ expect(calledUrl).toContain('geometries=geojson');
52
+ expect(calledUrl).toContain('alternatives=true');
53
+ expect(calledUrl).toContain('annotations=distance%2Cduration%2Cspeed');
54
+ expect(calledUrl).toContain('overview=full');
55
+ expect(calledUrl).toContain('exclude=ferry');
56
+ assertHeadersSent(mockFetch);
57
+ });
58
+ it('uses default parameters when not specified', async () => {
59
+ const mockFetch = setupFetch();
60
+ await new DirectionsTool().run({
61
+ coordinates: [
62
+ [-118.24, 34.05],
63
+ [-118.3, 34.02]
64
+ ]
65
+ });
66
+ const calledUrl = mockFetch.mock.calls[0][0];
67
+ expect(calledUrl).toContain('directions/v5/mapbox/driving-traffic');
68
+ expect(calledUrl).toContain('geometries=polyline');
69
+ expect(calledUrl).toContain('alternatives=false');
70
+ expect(calledUrl).not.toContain('annotations=');
71
+ expect(calledUrl).not.toContain('exclude=');
72
+ assertHeadersSent(mockFetch);
73
+ });
74
+ it('handles exclude parameter with point format', async () => {
75
+ const mockFetch = setupFetch();
76
+ await new DirectionsTool().run({
77
+ coordinates: [
78
+ [-74.0, 40.7],
79
+ [-73.9, 40.8]
80
+ ],
81
+ exclude: 'toll,point(-73.95 40.75)'
82
+ });
83
+ const calledUrl = mockFetch.mock.calls[0][0];
84
+ const comma = '%2C';
85
+ const space = '%20';
86
+ const openPar = '%28';
87
+ const closePar = '%29';
88
+ expect(calledUrl).toContain(`exclude=toll${comma}point${openPar}-73.95${space}40.75${closePar}`);
89
+ assertHeadersSent(mockFetch);
90
+ });
91
+ it('handles fetch errors gracefully', async () => {
92
+ const mockFetch = setupFetch({
93
+ ok: false,
94
+ status: 404,
95
+ statusText: 'Not Found'
96
+ });
97
+ const result = await new DirectionsTool().run({
98
+ coordinates: [
99
+ [-73.989, 40.733],
100
+ [-73.979, 40.743]
101
+ ]
102
+ });
103
+ expect(result.is_error).toBe(true);
104
+ expect(result.content[0]).toMatchObject({
105
+ type: 'text',
106
+ text: 'Internal error has occurred.'
107
+ });
108
+ assertHeadersSent(mockFetch);
109
+ });
110
+ it('validates coordinates constraints - minimum required', async () => {
111
+ const tool = new DirectionsTool();
112
+ // Test with only one coordinate (invalid)
113
+ await expect(tool.run({
114
+ coordinates: [[-73.989, 40.733]]
115
+ })).resolves.toMatchObject({
116
+ is_error: true
117
+ });
118
+ // Test with zero coordinates (invalid)
119
+ await expect(tool.run({
120
+ coordinates: []
121
+ })).resolves.toMatchObject({
122
+ is_error: true
123
+ });
124
+ });
125
+ it('validates coordinates constraints - maximum allowed', async () => {
126
+ const tool = new DirectionsTool();
127
+ // Create an array of 26 coordinates (one more than allowed)
128
+ const tooManyCoords = Array(26).fill([-73.989, 40.733]);
129
+ await expect(tool.run({
130
+ coordinates: tooManyCoords
131
+ })).resolves.toMatchObject({
132
+ is_error: true
133
+ });
134
+ });
135
+ it('successfully processes exactly 2 coordinates (minimum allowed)', async () => {
136
+ const mockFetch = setupFetch();
137
+ await new DirectionsTool().run({
138
+ coordinates: [
139
+ [-73.989, 40.733],
140
+ [-73.979, 40.743]
141
+ ]
142
+ });
143
+ const calledUrl = mockFetch.mock.calls[0][0];
144
+ expect(calledUrl).toContain('-73.989%2C40.733%3B-73.979%2C40.743');
145
+ assertHeadersSent(mockFetch);
146
+ });
147
+ it('successfully processes exactly 25 coordinates (maximum allowed)', async () => {
148
+ const mockFetch = setupFetch();
149
+ // Create an array of exactly 25 coordinates (maximum allowed)
150
+ const maxCoords = Array(25)
151
+ .fill(0)
152
+ .map((_, i) => [-74 + i * 0.01, 40 + i * 0.01]);
153
+ await new DirectionsTool().run({
154
+ coordinates: maxCoords
155
+ });
156
+ const calledUrl = mockFetch.mock.calls[0][0];
157
+ // Check that all coordinates are properly encoded
158
+ for (let i = 0; i < maxCoords.length; i++) {
159
+ const [lng, lat] = maxCoords[i];
160
+ const semicolon = i < 24 ? '%3B' : '';
161
+ const expectedCoord = `${lng}%2C${lat}` + semicolon;
162
+ expect(calledUrl).toContain(expectedCoord);
163
+ }
164
+ assertHeadersSent(mockFetch);
165
+ });
166
+ describe('walking parameters validations', () => {
167
+ it('accepts walking_speed with walking profile', async () => {
168
+ const mockFetch = setupFetch();
169
+ await new DirectionsTool().run({
170
+ coordinates: [
171
+ [-73.989, 40.733],
172
+ [-73.979, 40.743]
173
+ ],
174
+ routing_profile: 'walking',
175
+ walking_speed: 2.5
176
+ });
177
+ const calledUrl = mockFetch.mock.calls[0][0];
178
+ expect(calledUrl).toContain('walking_speed=2.5');
179
+ assertHeadersSent(mockFetch);
180
+ });
181
+ it('accepts walkway_bias with walking profile', async () => {
182
+ const mockFetch = setupFetch();
183
+ await new DirectionsTool().run({
184
+ coordinates: [
185
+ [-73.989, 40.733],
186
+ [-73.979, 40.743]
187
+ ],
188
+ routing_profile: 'walking',
189
+ walkway_bias: 0.8
190
+ });
191
+ const calledUrl = mockFetch.mock.calls[0][0];
192
+ expect(calledUrl).toContain('walkway_bias=0.8');
193
+ assertHeadersSent(mockFetch);
194
+ });
195
+ it('rejects walking_speed with non-walking profiles', async () => {
196
+ const tool = new DirectionsTool();
197
+ // Test with driving profile
198
+ await expect(tool.run({
199
+ coordinates: [
200
+ [-73.989, 40.733],
201
+ [-73.979, 40.743]
202
+ ],
203
+ routing_profile: 'driving',
204
+ walking_speed: 2.0
205
+ })).resolves.toMatchObject({
206
+ is_error: true
207
+ });
208
+ // Test with cycling profile
209
+ await expect(tool.run({
210
+ coordinates: [
211
+ [-73.989, 40.733],
212
+ [-73.979, 40.743]
213
+ ],
214
+ routing_profile: 'cycling',
215
+ walking_speed: 2.0
216
+ })).resolves.toMatchObject({
217
+ is_error: true
218
+ });
219
+ });
220
+ it('rejects walkway_bias with non-walking profiles', async () => {
221
+ const tool = new DirectionsTool();
222
+ // Test with driving-traffic profile
223
+ await expect(tool.run({
224
+ coordinates: [
225
+ [-73.989, 40.733],
226
+ [-73.979, 40.743]
227
+ ],
228
+ routing_profile: 'driving-traffic',
229
+ walkway_bias: 0.5
230
+ })).resolves.toMatchObject({
231
+ is_error: true
232
+ });
233
+ // Test with cycling profile
234
+ await expect(tool.run({
235
+ coordinates: [
236
+ [-73.989, 40.733],
237
+ [-73.979, 40.743]
238
+ ],
239
+ routing_profile: 'cycling',
240
+ walkway_bias: -0.8
241
+ })).resolves.toMatchObject({
242
+ is_error: true
243
+ });
244
+ });
245
+ it('validates walking_speed value ranges', async () => {
246
+ const tool = new DirectionsTool();
247
+ // Test with value below minimum (0.14 m/s)
248
+ await expect(tool.run({
249
+ coordinates: [
250
+ [-73.989, 40.733],
251
+ [-73.979, 40.743]
252
+ ],
253
+ routing_profile: 'walking',
254
+ walking_speed: 0.1
255
+ })).resolves.toMatchObject({
256
+ is_error: true
257
+ });
258
+ // Test with value above maximum (6.94 m/s)
259
+ await expect(tool.run({
260
+ coordinates: [
261
+ [-73.989, 40.733],
262
+ [-73.979, 40.743]
263
+ ],
264
+ routing_profile: 'walking',
265
+ walking_speed: 7.5
266
+ })).resolves.toMatchObject({
267
+ is_error: true
268
+ });
269
+ // Test with valid value
270
+ const mockFetch = setupFetch();
271
+ await tool.run({
272
+ coordinates: [
273
+ [-73.989, 40.733],
274
+ [-73.979, 40.743]
275
+ ],
276
+ routing_profile: 'walking',
277
+ walking_speed: 3.0
278
+ });
279
+ const calledUrl = mockFetch.mock.calls[0][0];
280
+ expect(calledUrl).toContain('walking_speed=3');
281
+ });
282
+ it('validates walkway_bias value ranges', async () => {
283
+ const tool = new DirectionsTool();
284
+ // Test with value below minimum (-1)
285
+ await expect(tool.run({
286
+ coordinates: [
287
+ [-73.989, 40.733],
288
+ [-73.979, 40.743]
289
+ ],
290
+ routing_profile: 'walking',
291
+ walkway_bias: -1.5
292
+ })).resolves.toMatchObject({
293
+ is_error: true
294
+ });
295
+ // Test with value above maximum (1)
296
+ await expect(tool.run({
297
+ coordinates: [
298
+ [-73.989, 40.733],
299
+ [-73.979, 40.743]
300
+ ],
301
+ routing_profile: 'walking',
302
+ walkway_bias: 1.2
303
+ })).resolves.toMatchObject({
304
+ is_error: true
305
+ });
306
+ // Test with valid values
307
+ const mockFetch = setupFetch();
308
+ await tool.run({
309
+ coordinates: [
310
+ [-73.989, 40.733],
311
+ [-73.979, 40.743]
312
+ ],
313
+ routing_profile: 'walking',
314
+ walkway_bias: -0.5
315
+ });
316
+ const calledUrl = mockFetch.mock.calls[0][0];
317
+ expect(calledUrl).toContain('walkway_bias=-0.5');
318
+ });
319
+ });
320
+ describe('exclude parameter and routing profile validations', () => {
321
+ it('accepts driving-specific exclusions with driving profiles', async () => {
322
+ const mockFetch = setupFetch();
323
+ const tool = new DirectionsTool();
324
+ // Test with driving profile
325
+ await expect(tool.run({
326
+ coordinates: [
327
+ [-73.989, 40.733],
328
+ [-73.979, 40.743]
329
+ ],
330
+ routing_profile: 'driving',
331
+ exclude: 'toll,motorway,unpaved'
332
+ })).resolves.not.toMatchObject({
333
+ is_error: true
334
+ });
335
+ // Test with driving-traffic profile
336
+ await expect(tool.run({
337
+ coordinates: [
338
+ [-73.989, 40.733],
339
+ [-73.979, 40.743]
340
+ ],
341
+ routing_profile: 'driving-traffic',
342
+ exclude: 'tunnel,country_border,state_border'
343
+ })).resolves.not.toMatchObject({
344
+ is_error: true
345
+ });
346
+ });
347
+ it('rejects driving-specific exclusions with non-driving profiles', async () => {
348
+ const tool = new DirectionsTool();
349
+ // Test with walking profile
350
+ await expect(tool.run({
351
+ coordinates: [
352
+ [-73.989, 40.733],
353
+ [-73.979, 40.743]
354
+ ],
355
+ routing_profile: 'walking',
356
+ exclude: 'toll'
357
+ })).resolves.toMatchObject({
358
+ is_error: true
359
+ });
360
+ // Test with cycling profile
361
+ await expect(tool.run({
362
+ coordinates: [
363
+ [-73.989, 40.733],
364
+ [-73.979, 40.743]
365
+ ],
366
+ routing_profile: 'cycling',
367
+ exclude: 'motorway'
368
+ })).resolves.toMatchObject({
369
+ is_error: true
370
+ });
371
+ });
372
+ it('accepts common exclusions with all routing profiles', async () => {
373
+ const mockFetch = setupFetch();
374
+ const tool = new DirectionsTool();
375
+ // Test with driving profile
376
+ await expect(tool.run({
377
+ coordinates: [
378
+ [-73.989, 40.733],
379
+ [-73.979, 40.743]
380
+ ],
381
+ routing_profile: 'driving',
382
+ exclude: 'ferry'
383
+ })).resolves.not.toMatchObject({
384
+ is_error: true
385
+ });
386
+ // Test with walking profile
387
+ await expect(tool.run({
388
+ coordinates: [
389
+ [-73.989, 40.733],
390
+ [-73.979, 40.743]
391
+ ],
392
+ routing_profile: 'walking',
393
+ exclude: 'ferry'
394
+ })).resolves.not.toMatchObject({
395
+ is_error: true
396
+ });
397
+ // Test with cycling profile
398
+ await expect(tool.run({
399
+ coordinates: [
400
+ [-73.989, 40.733],
401
+ [-73.979, 40.743]
402
+ ],
403
+ routing_profile: 'cycling',
404
+ exclude: 'cash_only_tolls'
405
+ })).resolves.not.toMatchObject({
406
+ is_error: true
407
+ });
408
+ });
409
+ it('accepts point exclusions with driving profiles and rejects with non-driving profiles', async () => {
410
+ const mockFetch = setupFetch();
411
+ const tool = new DirectionsTool();
412
+ // Test with driving profile - should work
413
+ await expect(tool.run({
414
+ coordinates: [
415
+ [-73.989, 40.733],
416
+ [-73.979, 40.743]
417
+ ],
418
+ routing_profile: 'driving',
419
+ exclude: 'point(-73.95 40.75)'
420
+ })).resolves.not.toMatchObject({
421
+ is_error: true
422
+ });
423
+ // Test with walking profile - should fail
424
+ await expect(tool.run({
425
+ coordinates: [
426
+ [-73.989, 40.733],
427
+ [-73.979, 40.743]
428
+ ],
429
+ routing_profile: 'walking',
430
+ exclude: 'point(-73.95 40.75)'
431
+ })).resolves.toMatchObject({
432
+ is_error: true
433
+ });
434
+ // Test with cycling profile - should fail
435
+ await expect(tool.run({
436
+ coordinates: [
437
+ [-73.989, 40.733],
438
+ [-73.979, 40.743]
439
+ ],
440
+ routing_profile: 'cycling',
441
+ exclude: 'point(-73.95 40.75)'
442
+ })).resolves.toMatchObject({
443
+ is_error: true
444
+ });
445
+ });
446
+ it('handles multiple exclusions in a single request correctly', async () => {
447
+ const mockFetch = setupFetch();
448
+ const tool = new DirectionsTool();
449
+ // All valid exclusions for driving profile
450
+ await expect(tool.run({
451
+ coordinates: [
452
+ [-73.989, 40.733],
453
+ [-73.979, 40.743]
454
+ ],
455
+ routing_profile: 'driving',
456
+ exclude: 'toll,motorway,ferry,cash_only_tolls,point(-73.95 40.75)'
457
+ })).resolves.not.toMatchObject({
458
+ is_error: true
459
+ });
460
+ // Mixed valid and invalid exclusions (ferry is valid for walking, toll is not)
461
+ await expect(tool.run({
462
+ coordinates: [
463
+ [-73.989, 40.733],
464
+ [-73.979, 40.743]
465
+ ],
466
+ routing_profile: 'walking',
467
+ exclude: 'ferry,toll'
468
+ })).resolves.toMatchObject({
469
+ is_error: true
470
+ });
471
+ // All valid exclusions for cycling profile
472
+ await expect(tool.run({
473
+ coordinates: [
474
+ [-73.989, 40.733],
475
+ [-73.979, 40.743]
476
+ ],
477
+ routing_profile: 'cycling',
478
+ exclude: 'ferry,cash_only_tolls'
479
+ })).resolves.not.toMatchObject({
480
+ is_error: true
481
+ });
482
+ });
483
+ });
484
+ describe('depart_at parameter validations', () => {
485
+ it('accepts depart_at with driving profiles', async () => {
486
+ const mockFetch = setupFetch();
487
+ const tool = new DirectionsTool();
488
+ const validDateTime = '2025-06-05T10:30:00Z';
489
+ // Test with driving profile
490
+ await expect(tool.run({
491
+ coordinates: [
492
+ [-73.989, 40.733],
493
+ [-73.979, 40.743]
494
+ ],
495
+ routing_profile: 'driving',
496
+ depart_at: validDateTime
497
+ })).resolves.not.toMatchObject({
498
+ is_error: true
499
+ });
500
+ const calledUrlDriving = mockFetch.mock.calls[0][0];
501
+ expect(calledUrlDriving).toContain(`depart_at=${encodeURIComponent(validDateTime)}`);
502
+ // Test with driving-traffic profile
503
+ await expect(tool.run({
504
+ coordinates: [
505
+ [-73.989, 40.733],
506
+ [-73.979, 40.743]
507
+ ],
508
+ routing_profile: 'driving-traffic',
509
+ depart_at: validDateTime
510
+ })).resolves.not.toMatchObject({
511
+ is_error: true
512
+ });
513
+ const calledUrlTraffic = mockFetch.mock.calls[1][0];
514
+ expect(calledUrlTraffic).toContain(`depart_at=${encodeURIComponent(validDateTime)}`);
515
+ });
516
+ describe('vehicle dimension parameters validations', () => {
517
+ it('accepts vehicle dimensions with driving profiles', async () => {
518
+ const mockFetch = setupFetch();
519
+ const tool = new DirectionsTool();
520
+ // Test with driving profile
521
+ await expect(tool.run({
522
+ coordinates: [
523
+ [-73.989, 40.733],
524
+ [-73.979, 40.743]
525
+ ],
526
+ routing_profile: 'driving',
527
+ max_height: 4.5,
528
+ max_width: 2.5,
529
+ max_weight: 7.8
530
+ })).resolves.not.toMatchObject({
531
+ is_error: true
532
+ });
533
+ const calledUrlDriving = mockFetch.mock.calls[0][0];
534
+ expect(calledUrlDriving).toContain('max_height=4.5');
535
+ expect(calledUrlDriving).toContain('max_width=2.5');
536
+ expect(calledUrlDriving).toContain('max_weight=7.8');
537
+ // Test with driving-traffic profile
538
+ await expect(tool.run({
539
+ coordinates: [
540
+ [-73.989, 40.733],
541
+ [-73.979, 40.743]
542
+ ],
543
+ routing_profile: 'driving-traffic',
544
+ max_height: 3.2
545
+ })).resolves.not.toMatchObject({
546
+ is_error: true
547
+ });
548
+ const calledUrlTraffic = mockFetch.mock.calls[1][0];
549
+ expect(calledUrlTraffic).toContain('max_height=3.2');
550
+ });
551
+ it('rejects vehicle dimensions with non-driving profiles', async () => {
552
+ const tool = new DirectionsTool();
553
+ // Test with walking profile
554
+ await expect(tool.run({
555
+ coordinates: [
556
+ [-73.989, 40.733],
557
+ [-73.979, 40.743]
558
+ ],
559
+ routing_profile: 'walking',
560
+ max_height: 4.5
561
+ })).resolves.toMatchObject({
562
+ is_error: true
563
+ });
564
+ // Test with cycling profile
565
+ await expect(tool.run({
566
+ coordinates: [
567
+ [-73.989, 40.733],
568
+ [-73.979, 40.743]
569
+ ],
570
+ routing_profile: 'cycling',
571
+ max_width: 2.0
572
+ })).resolves.toMatchObject({
573
+ is_error: true
574
+ });
575
+ });
576
+ it('validates dimension value ranges', async () => {
577
+ const tool = new DirectionsTool();
578
+ // Test invalid height (too high)
579
+ await expect(tool.run({
580
+ coordinates: [
581
+ [-73.989, 40.733],
582
+ [-73.979, 40.743]
583
+ ],
584
+ routing_profile: 'driving',
585
+ max_height: 15.0
586
+ })).resolves.toMatchObject({
587
+ is_error: true
588
+ });
589
+ // Test invalid width (negative)
590
+ await expect(tool.run({
591
+ coordinates: [
592
+ [-73.989, 40.733],
593
+ [-73.979, 40.743]
594
+ ],
595
+ routing_profile: 'driving',
596
+ max_width: -1.0
597
+ })).resolves.toMatchObject({
598
+ is_error: true
599
+ });
600
+ // Test invalid weight (too heavy)
601
+ await expect(tool.run({
602
+ coordinates: [
603
+ [-73.989, 40.733],
604
+ [-73.979, 40.743]
605
+ ],
606
+ routing_profile: 'driving',
607
+ max_weight: 150.0
608
+ })).resolves.toMatchObject({
609
+ is_error: true
610
+ });
611
+ });
612
+ });
613
+ it('rejects depart_at with non-driving profiles', async () => {
614
+ const tool = new DirectionsTool();
615
+ const validDateTime = '2025-06-05T10:30:00Z';
616
+ // Test with walking profile
617
+ await expect(tool.run({
618
+ coordinates: [
619
+ [-73.989, 40.733],
620
+ [-73.979, 40.743]
621
+ ],
622
+ routing_profile: 'walking',
623
+ depart_at: validDateTime
624
+ })).resolves.toMatchObject({
625
+ is_error: true
626
+ });
627
+ // Test with cycling profile
628
+ await expect(tool.run({
629
+ coordinates: [
630
+ [-73.989, 40.733],
631
+ [-73.979, 40.743]
632
+ ],
633
+ routing_profile: 'cycling',
634
+ depart_at: validDateTime
635
+ })).resolves.toMatchObject({
636
+ is_error: true
637
+ });
638
+ });
639
+ it('accepts valid date-time formats', async () => {
640
+ const mockFetch = setupFetch();
641
+ const tool = new DirectionsTool();
642
+ const baseCoordinates = [
643
+ [-73.989, 40.733],
644
+ [-73.979, 40.743]
645
+ ];
646
+ // Format 1: YYYY-MM-DDThh:mm:ssZ
647
+ await expect(tool.run({
648
+ coordinates: baseCoordinates,
649
+ depart_at: '2025-06-05T10:30:00Z'
650
+ })).resolves.not.toMatchObject({
651
+ is_error: true
652
+ });
653
+ // Format 2: YYYY-MM-DDThh:mmss±hh:mm
654
+ await expect(tool.run({
655
+ coordinates: baseCoordinates,
656
+ depart_at: '2025-06-05T10:30:00+02:00'
657
+ })).resolves.not.toMatchObject({
658
+ is_error: true
659
+ });
660
+ // Format 3: YYYY-MM-DDThh:mm
661
+ await expect(tool.run({
662
+ coordinates: baseCoordinates,
663
+ depart_at: '2025-06-05T10:30'
664
+ })).resolves.not.toMatchObject({
665
+ is_error: true
666
+ });
667
+ });
668
+ it('rejects invalid date-time formats', async () => {
669
+ const tool = new DirectionsTool();
670
+ const baseCoordinates = [
671
+ [-73.989, 40.733],
672
+ [-73.979, 40.743]
673
+ ];
674
+ // Invalid format examples
675
+ const invalidFormats = [
676
+ '2025/06/05 10:30:00', // Wrong delimiter
677
+ '2025-06-05 10:30:00', // Missing T
678
+ '2025-06-05T10:30:00+0200', // Missing colon in timezone
679
+ '25-6-5T10:30:00Z', // Incorrect date format
680
+ '2025-06-05T10:30:00ZZ', // Double timezone
681
+ '2025-06-05', // Missing time
682
+ '10:30:00' // Missing date
683
+ ];
684
+ // Test each format separately
685
+ for (let i = 0; i < invalidFormats.length; i++) {
686
+ const format = invalidFormats[i];
687
+ // Test each invalid format individually for better error reporting
688
+ const result = await tool.run({
689
+ coordinates: baseCoordinates,
690
+ depart_at: format
691
+ });
692
+ expect(result.is_error).toBe(true);
693
+ }
694
+ });
695
+ it('rejects dates with invalid components', async () => {
696
+ const tool = new DirectionsTool();
697
+ const baseCoordinates = [
698
+ [-73.989, 40.733],
699
+ [-73.979, 40.743]
700
+ ];
701
+ // Invalid time components
702
+ const invalidDates = [
703
+ '2025-13-05T10:30:00Z', // Invalid month (13)
704
+ '2025-06-32T10:30:00Z', // Invalid day (32)
705
+ '2025-06-05T24:30:00Z', // Invalid hour (24)
706
+ '2025-06-05T10:60:00Z', // Invalid minute (60)
707
+ '2025-06-05T10:30:60Z' // Invalid second (60)
708
+ ];
709
+ for (const date of invalidDates) {
710
+ await expect(tool.run({
711
+ coordinates: baseCoordinates,
712
+ depart_at: date
713
+ })).resolves.toMatchObject({
714
+ is_error: true
715
+ });
716
+ }
717
+ });
718
+ });
719
+ describe('arrive_by parameter validations', () => {
720
+ it('accepts arrive_by with driving profile only', async () => {
721
+ const validDateTime = '2025-06-05T10:30:00Z';
722
+ const mockFetch = setupFetch();
723
+ // Test with driving profile - should work
724
+ await new DirectionsTool().run({
725
+ coordinates: [
726
+ [-74.1, 40.7],
727
+ [-74.2, 40.8]
728
+ ],
729
+ routing_profile: 'driving',
730
+ arrive_by: validDateTime
731
+ });
732
+ expect(mockFetch).toHaveBeenCalledTimes(1);
733
+ const calledUrl = mockFetch.mock.calls[0][0];
734
+ expect(calledUrl).toContain(`arrive_by=${encodeURIComponent(validDateTime)}`);
735
+ });
736
+ it('rejects arrive_by with non-driving profiles', async () => {
737
+ const validDateTime = '2025-06-05T10:30:00Z';
738
+ // Test with driving-traffic profile
739
+ const result1 = await new DirectionsTool().run({
740
+ coordinates: [
741
+ [-74.1, 40.7],
742
+ [-74.2, 40.8]
743
+ ],
744
+ routing_profile: 'driving-traffic',
745
+ arrive_by: validDateTime
746
+ });
747
+ expect(result1.is_error).toBe(true);
748
+ // Test with walking profile
749
+ const result2 = await new DirectionsTool().run({
750
+ coordinates: [
751
+ [-74.1, 40.7],
752
+ [-74.2, 40.8]
753
+ ],
754
+ routing_profile: 'walking',
755
+ arrive_by: validDateTime
756
+ });
757
+ expect(result2.is_error).toBe(true);
758
+ // Test with cycling profile
759
+ const result3 = await new DirectionsTool().run({
760
+ coordinates: [
761
+ [-74.1, 40.7],
762
+ [-74.2, 40.8]
763
+ ],
764
+ routing_profile: 'cycling',
765
+ arrive_by: validDateTime
766
+ });
767
+ expect(result3.is_error).toBe(true);
768
+ });
769
+ it('rejects when both arrive_by and depart_at are provided', async () => {
770
+ const result = await new DirectionsTool().run({
771
+ coordinates: [
772
+ [-74.1, 40.7],
773
+ [-74.2, 40.8]
774
+ ],
775
+ routing_profile: 'driving',
776
+ depart_at: '2025-06-05T09:30:00Z',
777
+ arrive_by: '2025-06-05T10:30:00Z'
778
+ });
779
+ expect(result.is_error).toBe(true);
780
+ });
781
+ it('accepts valid ISO 8601 formats for arrive_by', async () => {
782
+ const mockFetch = setupFetch();
783
+ // Test with Z format
784
+ await new DirectionsTool().run({
785
+ coordinates: [
786
+ [-74.1, 40.7],
787
+ [-74.2, 40.8]
788
+ ],
789
+ routing_profile: 'driving',
790
+ arrive_by: '2025-06-05T10:30:00Z'
791
+ });
792
+ expect(mockFetch).toHaveBeenCalledTimes(1);
793
+ mockFetch.mockClear();
794
+ // Test with timezone offset format
795
+ await new DirectionsTool().run({
796
+ coordinates: [
797
+ [-74.1, 40.7],
798
+ [-74.2, 40.8]
799
+ ],
800
+ routing_profile: 'driving',
801
+ arrive_by: '2025-06-05T10:30:00+02:00'
802
+ });
803
+ expect(mockFetch).toHaveBeenCalledTimes(1);
804
+ mockFetch.mockClear();
805
+ // Test with simple time format (no seconds, no timezone)
806
+ await new DirectionsTool().run({
807
+ coordinates: [
808
+ [-74.1, 40.7],
809
+ [-74.2, 40.8]
810
+ ],
811
+ routing_profile: 'driving',
812
+ arrive_by: '2025-06-05T10:30'
813
+ });
814
+ expect(mockFetch).toHaveBeenCalledTimes(1);
815
+ });
816
+ it('rejects invalid formats for arrive_by', async () => {
817
+ const invalidFormats = [
818
+ // Invalid date formats
819
+ '2025/06/05T10:30:00Z',
820
+ '5-6-2025T10:30:00Z',
821
+ '2025-6-5T10:30:00Z',
822
+ // Invalid time formats
823
+ '2025-06-05T1:30:00Z',
824
+ '2025-06-05T10-30-00Z',
825
+ // Missing T separator
826
+ '2025-06-05 10:30:00Z',
827
+ // Completely wrong formats
828
+ '10:30 June 5, 2025',
829
+ 'June 5, 2025 10:30 AM',
830
+ 'Tomorrow at 10:30'
831
+ ];
832
+ for (const format of invalidFormats) {
833
+ const result = await new DirectionsTool().run({
834
+ coordinates: [
835
+ [-74.1, 40.7],
836
+ [-74.2, 40.8]
837
+ ],
838
+ routing_profile: 'driving',
839
+ arrive_by: format
840
+ });
841
+ expect(result.is_error).toBe(true);
842
+ }
843
+ });
844
+ it('validates date and time component ranges for arrive_by', async () => {
845
+ const invalidDates = [
846
+ '2025-13-05T10:30:00Z', // Invalid month (13)
847
+ '2025-06-32T10:30:00Z', // Invalid day (32)
848
+ '2025-02-30T10:30:00Z', // Invalid day in February
849
+ '2025-06-05T24:30:00Z', // Invalid hour (24)
850
+ '2025-06-05T10:60:00Z', // Invalid minute (60)
851
+ '2025-06-05T10:30:60Z' // Invalid second (60)
852
+ ];
853
+ for (const date of invalidDates) {
854
+ const result = await new DirectionsTool().run({
855
+ coordinates: [
856
+ [-74.1, 40.7],
857
+ [-74.2, 40.8]
858
+ ],
859
+ routing_profile: 'driving',
860
+ arrive_by: date
861
+ });
862
+ expect(result.is_error).toBe(true);
863
+ }
864
+ });
865
+ });
866
+ });
867
+ //# sourceMappingURL=DirectionsTool.test.js.map