@xenterprises/fastify-xgeocode 1.0.1

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.
@@ -0,0 +1,901 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert";
3
+ import Fastify from "fastify";
4
+ import xGeocode from "../src/xGeocode.js";
5
+
6
+ test("xGeocode Plugin - registers successfully with apiKey", async () => {
7
+ const fastify = Fastify({ logger: false });
8
+ try {
9
+ await fastify.register(xGeocode, {
10
+ apiKey: "test-api-key"
11
+ });
12
+ assert.ok(true, "Plugin registered successfully");
13
+ } finally {
14
+ await fastify.close();
15
+ }
16
+ });
17
+
18
+ test("xGeocode Plugin - throws error when apiKey is missing", async () => {
19
+ const fastify = Fastify({ logger: false });
20
+ try {
21
+ await assert.rejects(
22
+ async () => await fastify.register(xGeocode, {}),
23
+ { message: /apiKey is required/ }
24
+ );
25
+ } finally {
26
+ await fastify.close();
27
+ }
28
+ });
29
+
30
+ test("xGeocode Plugin - can be disabled with active: false", async () => {
31
+ const fastify = Fastify({ logger: false });
32
+ try {
33
+ await fastify.register(xGeocode, {
34
+ active: false,
35
+ apiKey: "test-api-key"
36
+ });
37
+ assert.ok(!fastify.geocode, "Geocode decorator not added when disabled");
38
+ } finally {
39
+ await fastify.close();
40
+ }
41
+ });
42
+
43
+ test("xGeocode Plugin - provides xGeocode decorator", async () => {
44
+ const fastify = Fastify({ logger: false });
45
+ try {
46
+ await fastify.register(xGeocode, {
47
+ apiKey: "test-api-key"
48
+ });
49
+ assert.ok(fastify.xGeocode, "xGeocode decorator is available");
50
+ assert.ok(typeof fastify.xGeocode.getLatLongByZip === "function", "getLatLongByZip method exists");
51
+ } finally {
52
+ await fastify.close();
53
+ }
54
+ });
55
+
56
+ test("xGeocode Validation - rejects invalid zip code (too short)", async () => {
57
+ const fastify = Fastify({ logger: false });
58
+ try {
59
+ await fastify.register(xGeocode, {
60
+ apiKey: "test-api-key"
61
+ });
62
+
63
+ await assert.rejects(
64
+ () => fastify.xGeocode.getLatLongByZip("123"),
65
+ { message: /Invalid zip code format/ }
66
+ );
67
+ } finally {
68
+ await fastify.close();
69
+ }
70
+ });
71
+
72
+ test("xGeocode Validation - rejects invalid zip code (letters)", async () => {
73
+ const fastify = Fastify({ logger: false });
74
+ try {
75
+ await fastify.register(xGeocode, {
76
+ apiKey: "test-api-key"
77
+ });
78
+
79
+ await assert.rejects(
80
+ () => fastify.xGeocode.getLatLongByZip("1000A"),
81
+ { message: /Invalid zip code format/ }
82
+ );
83
+ } finally {
84
+ await fastify.close();
85
+ }
86
+ });
87
+
88
+ test("xGeocode Validation - accepts 5-digit zip code format", async () => {
89
+ const fastify = Fastify({ logger: false });
90
+ try {
91
+ await fastify.register(xGeocode, {
92
+ apiKey: "test-api-key"
93
+ });
94
+
95
+ // Mock fetch to avoid actual API call
96
+ const originalFetch = global.fetch;
97
+ global.fetch = async () => ({
98
+ ok: true,
99
+ json: async () => ({
100
+ results: [{
101
+ location: { lat: 40.7506, lng: -73.9972 },
102
+ formatted_address: "New York, NY 10001",
103
+ address_components: {
104
+ city: "New York",
105
+ county: "New York County",
106
+ state: "NY",
107
+ country: "US",
108
+ zip: "10001"
109
+ }
110
+ }]
111
+ })
112
+ });
113
+
114
+ try {
115
+ const result = await fastify.xGeocode.getLatLongByZip("10001");
116
+ assert.ok(result, "Accepts valid 5-digit zip code");
117
+ assert.equal(result.zip, "10001", "Returns original zip code");
118
+ assert.equal(result.lat, 40.7506, "Returns latitude");
119
+ assert.equal(result.lng, -73.9972, "Returns longitude");
120
+ } finally {
121
+ global.fetch = originalFetch;
122
+ }
123
+ } finally {
124
+ await fastify.close();
125
+ }
126
+ });
127
+
128
+ test("xGeocode Validation - accepts 9-digit zip code (ZIP+4) format", async () => {
129
+ const fastify = Fastify({ logger: false });
130
+ try {
131
+ await fastify.register(xGeocode, {
132
+ apiKey: "test-api-key"
133
+ });
134
+
135
+ // Mock fetch
136
+ const originalFetch = global.fetch;
137
+ global.fetch = async () => ({
138
+ ok: true,
139
+ json: async () => ({
140
+ results: [{
141
+ location: { lat: 40.7506, lng: -73.9972 },
142
+ formatted_address: "New York, NY 10001-1234",
143
+ address_components: {
144
+ city: "New York",
145
+ county: "New York County",
146
+ state: "NY",
147
+ country: "US",
148
+ zip: "10001-1234"
149
+ }
150
+ }]
151
+ })
152
+ });
153
+
154
+ try {
155
+ const result = await fastify.xGeocode.getLatLongByZip("10001-1234");
156
+ assert.ok(result, "Accepts valid 9-digit zip code");
157
+ assert.equal(result.zip, "10001-1234", "Returns original zip code format");
158
+ assert.equal(result.lat, 40.7506, "Returns latitude");
159
+ assert.equal(result.lng, -73.9972, "Returns longitude");
160
+ } finally {
161
+ global.fetch = originalFetch;
162
+ }
163
+ } finally {
164
+ await fastify.close();
165
+ }
166
+ });
167
+
168
+ test("xGeocode Validation - rejects null or undefined zip code", async () => {
169
+ const fastify = Fastify({ logger: false });
170
+ try {
171
+ await fastify.register(xGeocode, {
172
+ apiKey: "test-api-key"
173
+ });
174
+
175
+ await assert.rejects(
176
+ () => fastify.xGeocode.getLatLongByZip(null),
177
+ { message: /Invalid input/ }
178
+ );
179
+ } finally {
180
+ await fastify.close();
181
+ }
182
+ });
183
+
184
+ test("xGeocode Error Handling - handles API errors gracefully", async () => {
185
+ const fastify = Fastify({ logger: false });
186
+ try {
187
+ await fastify.register(xGeocode, {
188
+ apiKey: "test-api-key"
189
+ });
190
+
191
+ // Mock fetch to return error status
192
+ const originalFetch = global.fetch;
193
+ global.fetch = async () => ({
194
+ ok: false,
195
+ status: 401
196
+ });
197
+
198
+ try {
199
+ await assert.rejects(
200
+ () => fastify.xGeocode.getLatLongByZip("10001"),
201
+ { message: /Geocoding/ }
202
+ );
203
+ } finally {
204
+ global.fetch = originalFetch;
205
+ }
206
+ } finally {
207
+ await fastify.close();
208
+ }
209
+ });
210
+
211
+ test("xGeocode Error Handling - handles empty results", async () => {
212
+ const fastify = Fastify({ logger: false });
213
+ try {
214
+ await fastify.register(xGeocode, {
215
+ apiKey: "test-api-key"
216
+ });
217
+
218
+ // Mock fetch to return empty results
219
+ const originalFetch = global.fetch;
220
+ global.fetch = async () => ({
221
+ ok: true,
222
+ json: async () => ({ results: [] })
223
+ });
224
+
225
+ try {
226
+ await assert.rejects(
227
+ () => fastify.xGeocode.getLatLongByZip("99999"),
228
+ { message: /No results found/ }
229
+ );
230
+ } finally {
231
+ global.fetch = originalFetch;
232
+ }
233
+ } finally {
234
+ await fastify.close();
235
+ }
236
+ });
237
+
238
+ test("xGeocode Response - returns complete location object", async () => {
239
+ const fastify = Fastify({ logger: false });
240
+ try {
241
+ await fastify.register(xGeocode, {
242
+ apiKey: "test-api-key"
243
+ });
244
+
245
+ // Mock fetch
246
+ const originalFetch = global.fetch;
247
+ global.fetch = async () => ({
248
+ ok: true,
249
+ json: async () => ({
250
+ results: [{
251
+ location: { lat: 40.7506, lng: -73.9972 },
252
+ formatted_address: "New York, NY 10001",
253
+ address_components: {
254
+ city: "New York",
255
+ county: "New York County",
256
+ state: "NY",
257
+ country: "US",
258
+ zip: "10001"
259
+ }
260
+ }]
261
+ })
262
+ });
263
+
264
+ try {
265
+ const result = await fastify.xGeocode.getLatLongByZip("10001");
266
+
267
+ assert.equal(result.zip, "10001", "Contains zip");
268
+ assert.equal(result.lat, 40.7506, "Contains latitude");
269
+ assert.equal(result.lng, -73.9972, "Contains longitude");
270
+ assert.equal(result.city, "New York", "Contains city");
271
+ assert.equal(result.county, "New York County", "Contains county");
272
+ assert.equal(result.state, "NY", "Contains state");
273
+ assert.equal(result.country, "US", "Contains country");
274
+ assert.ok(result.addressComponents, "Contains full address components");
275
+ } finally {
276
+ global.fetch = originalFetch;
277
+ }
278
+ } finally {
279
+ await fastify.close();
280
+ }
281
+ });
282
+
283
+ test("xGeocode Response - handles null address components gracefully", async () => {
284
+ const fastify = Fastify({ logger: false });
285
+ try {
286
+ await fastify.register(xGeocode, {
287
+ apiKey: "test-api-key"
288
+ });
289
+
290
+ // Mock fetch with missing address components
291
+ const originalFetch = global.fetch;
292
+ global.fetch = async () => ({
293
+ ok: true,
294
+ json: async () => ({
295
+ results: [{
296
+ location: { lat: 40.7506, lng: -73.9972 },
297
+ address_components: {
298
+ city: undefined,
299
+ county: undefined,
300
+ state: "NY",
301
+ country: "US"
302
+ }
303
+ }]
304
+ })
305
+ });
306
+
307
+ try {
308
+ const result = await fastify.xGeocode.getLatLongByZip("10001");
309
+
310
+ assert.equal(result.city, null, "Missing city becomes null");
311
+ assert.equal(result.county, null, "Missing county becomes null");
312
+ assert.equal(result.state, "NY", "Present values preserved");
313
+ } finally {
314
+ global.fetch = originalFetch;
315
+ }
316
+ } finally {
317
+ await fastify.close();
318
+ }
319
+ });
320
+
321
+ test("xGeocode Trim - trims whitespace from zip codes", async () => {
322
+ const fastify = Fastify({ logger: false });
323
+ try {
324
+ await fastify.register(xGeocode, {
325
+ apiKey: "test-api-key"
326
+ });
327
+
328
+ // Mock fetch
329
+ const originalFetch = global.fetch;
330
+ global.fetch = async () => ({
331
+ ok: true,
332
+ json: async () => ({
333
+ results: [{
334
+ location: { lat: 40.7506, lng: -73.9972 },
335
+ formatted_address: "New York, NY 10001",
336
+ address_components: {
337
+ city: "New York",
338
+ county: "New York County",
339
+ state: "NY",
340
+ country: "US",
341
+ zip: "10001"
342
+ }
343
+ }]
344
+ })
345
+ });
346
+
347
+ try {
348
+ const result = await fastify.xGeocode.getLatLongByZip(" 10001 ");
349
+ assert.equal(result.zip, "10001", "Whitespace trimmed from result");
350
+ } finally {
351
+ global.fetch = originalFetch;
352
+ }
353
+ } finally {
354
+ await fastify.close();
355
+ }
356
+ });
357
+
358
+ // getLatLongByAddress() Tests
359
+ test("xGeocode getLatLongByAddress - accepts valid address", async () => {
360
+ const fastify = Fastify({ logger: false });
361
+ try {
362
+ await fastify.register(xGeocode, {
363
+ apiKey: "test-api-key"
364
+ });
365
+
366
+ // Mock fetch
367
+ const originalFetch = global.fetch;
368
+ global.fetch = async () => ({
369
+ ok: true,
370
+ json: async () => ({
371
+ results: [{
372
+ location: { lat: 40.7128, lng: -74.0060 },
373
+ formatted_address: "123 Main St, New York, NY 10001",
374
+ address_components: {
375
+ city: "New York",
376
+ county: "New York County",
377
+ state: "NY",
378
+ country: "US",
379
+ zip: "10001"
380
+ }
381
+ }]
382
+ })
383
+ });
384
+
385
+ try {
386
+ const result = await fastify.xGeocode.getLatLongByAddress("123 Main St, New York, NY");
387
+ assert.ok(result, "Returns result for valid address");
388
+ assert.equal(result.lat, 40.7128, "Contains latitude");
389
+ assert.equal(result.lng, -74.0060, "Contains longitude");
390
+ assert.equal(result.city, "New York", "Contains city");
391
+ } finally {
392
+ global.fetch = originalFetch;
393
+ }
394
+ } finally {
395
+ await fastify.close();
396
+ }
397
+ });
398
+
399
+ test("xGeocode getLatLongByAddress - rejects address with less than 3 characters", async () => {
400
+ const fastify = Fastify({ logger: false });
401
+ try {
402
+ await fastify.register(xGeocode, {
403
+ apiKey: "test-api-key"
404
+ });
405
+
406
+ await assert.rejects(
407
+ () => fastify.xGeocode.getLatLongByAddress("12"),
408
+ { message: /minimum 3 characters/ }
409
+ );
410
+ } finally {
411
+ await fastify.close();
412
+ }
413
+ });
414
+
415
+ test("xGeocode getLatLongByAddress - rejects null address", async () => {
416
+ const fastify = Fastify({ logger: false });
417
+ try {
418
+ await fastify.register(xGeocode, {
419
+ apiKey: "test-api-key"
420
+ });
421
+
422
+ await assert.rejects(
423
+ () => fastify.xGeocode.getLatLongByAddress(null),
424
+ { message: /Invalid input/ }
425
+ );
426
+ } finally {
427
+ await fastify.close();
428
+ }
429
+ });
430
+
431
+ test("xGeocode getLatLongByAddress - trims whitespace from address", async () => {
432
+ const fastify = Fastify({ logger: false });
433
+ try {
434
+ await fastify.register(xGeocode, {
435
+ apiKey: "test-api-key"
436
+ });
437
+
438
+ // Mock fetch
439
+ const originalFetch = global.fetch;
440
+ global.fetch = async () => ({
441
+ ok: true,
442
+ json: async () => ({
443
+ results: [{
444
+ location: { lat: 40.7128, lng: -74.0060 },
445
+ formatted_address: "123 Main St, New York, NY 10001",
446
+ address_components: {
447
+ city: "New York",
448
+ county: "New York County",
449
+ state: "NY",
450
+ country: "US",
451
+ zip: "10001"
452
+ }
453
+ }]
454
+ })
455
+ });
456
+
457
+ try {
458
+ const result = await fastify.xGeocode.getLatLongByAddress(" 123 Main St ");
459
+ assert.ok(result, "Successfully processes address with whitespace");
460
+ } finally {
461
+ global.fetch = originalFetch;
462
+ }
463
+ } finally {
464
+ await fastify.close();
465
+ }
466
+ });
467
+
468
+ // getReverseGeocode() Tests
469
+ test("xGeocode getReverseGeocode - accepts valid coordinates", async () => {
470
+ const fastify = Fastify({ logger: false });
471
+ try {
472
+ await fastify.register(xGeocode, {
473
+ apiKey: "test-api-key"
474
+ });
475
+
476
+ // Mock fetch
477
+ const originalFetch = global.fetch;
478
+ global.fetch = async () => ({
479
+ ok: true,
480
+ json: async () => ({
481
+ results: [{
482
+ location: { lat: 40.7128, lng: -74.0060 },
483
+ formatted_address: "123 Main St, New York, NY 10001",
484
+ address_components: {
485
+ city: "New York",
486
+ county: "New York County",
487
+ state: "NY",
488
+ country: "US",
489
+ zip: "10001"
490
+ }
491
+ }]
492
+ })
493
+ });
494
+
495
+ try {
496
+ const result = await fastify.xGeocode.getReverseGeocode(40.7128, -74.0060);
497
+ assert.ok(result, "Returns result for valid coordinates");
498
+ assert.equal(result.city, "New York", "Contains city");
499
+ assert.equal(result.state, "NY", "Contains state");
500
+ } finally {
501
+ global.fetch = originalFetch;
502
+ }
503
+ } finally {
504
+ await fastify.close();
505
+ }
506
+ });
507
+
508
+ test("xGeocode getReverseGeocode - rejects invalid latitude", async () => {
509
+ const fastify = Fastify({ logger: false });
510
+ try {
511
+ await fastify.register(xGeocode, {
512
+ apiKey: "test-api-key"
513
+ });
514
+
515
+ await assert.rejects(
516
+ () => fastify.xGeocode.getReverseGeocode(95, -74.0060),
517
+ { message: /Invalid latitude/ }
518
+ );
519
+ } finally {
520
+ await fastify.close();
521
+ }
522
+ });
523
+
524
+ test("xGeocode getReverseGeocode - rejects invalid longitude", async () => {
525
+ const fastify = Fastify({ logger: false });
526
+ try {
527
+ await fastify.register(xGeocode, {
528
+ apiKey: "test-api-key"
529
+ });
530
+
531
+ await assert.rejects(
532
+ () => fastify.xGeocode.getReverseGeocode(40.7128, -185),
533
+ { message: /Invalid longitude/ }
534
+ );
535
+ } finally {
536
+ await fastify.close();
537
+ }
538
+ });
539
+
540
+ test("xGeocode getReverseGeocode - rejects non-numeric coordinates", async () => {
541
+ const fastify = Fastify({ logger: false });
542
+ try {
543
+ await fastify.register(xGeocode, {
544
+ apiKey: "test-api-key"
545
+ });
546
+
547
+ await assert.rejects(
548
+ () => fastify.xGeocode.getReverseGeocode("invalid", "coordinates"),
549
+ { message: /must be numbers/ }
550
+ );
551
+ } finally {
552
+ await fastify.close();
553
+ }
554
+ });
555
+
556
+ // getDistance() Tests
557
+ test("xGeocode getDistance - calculates distance between two points", async () => {
558
+ const fastify = Fastify({ logger: false });
559
+ try {
560
+ await fastify.register(xGeocode, {
561
+ apiKey: "test-api-key"
562
+ });
563
+
564
+ const result = fastify.xGeocode.getDistance(40.7128, -74.0060, 40.7614, -73.9776);
565
+ assert.ok(result, "Returns distance result");
566
+ assert.ok(result.kilometers, "Contains kilometers");
567
+ assert.ok(result.miles, "Contains miles");
568
+ assert.ok(result.meters, "Contains meters");
569
+ assert.ok(result.kilometers > 0, "Distance is greater than zero");
570
+ } finally {
571
+ await fastify.close();
572
+ }
573
+ });
574
+
575
+ test("xGeocode getDistance - validates coordinate ranges", async () => {
576
+ const fastify = Fastify({ logger: false });
577
+ try {
578
+ await fastify.register(xGeocode, {
579
+ apiKey: "test-api-key"
580
+ });
581
+
582
+ assert.throws(
583
+ () => fastify.xGeocode.getDistance(100, -74.0060, 40.7614, -73.9776),
584
+ { message: /Invalid latitude/ }
585
+ );
586
+ } finally {
587
+ await fastify.close();
588
+ }
589
+ });
590
+
591
+ test("xGeocode getDistance - handles string coordinates as numbers", async () => {
592
+ const fastify = Fastify({ logger: false });
593
+ try {
594
+ await fastify.register(xGeocode, {
595
+ apiKey: "test-api-key"
596
+ });
597
+
598
+ const result = fastify.xGeocode.getDistance("40.7128", "-74.0060", "40.7614", "-73.9776");
599
+ assert.ok(result, "Accepts string coordinates that parse as numbers");
600
+ assert.ok(result.kilometers > 0, "Calculates valid distance");
601
+ } finally {
602
+ await fastify.close();
603
+ }
604
+ });
605
+
606
+ test("xGeocode getDistance - rejects non-numeric coordinates", async () => {
607
+ const fastify = Fastify({ logger: false });
608
+ try {
609
+ await fastify.register(xGeocode, {
610
+ apiKey: "test-api-key"
611
+ });
612
+
613
+ assert.throws(
614
+ () => fastify.xGeocode.getDistance("invalid", "-74.0060", "40.7614", "-73.9776"),
615
+ { message: /must be numbers/ }
616
+ );
617
+ } finally {
618
+ await fastify.close();
619
+ }
620
+ });
621
+
622
+ // batchGeocode() Tests
623
+ test("xGeocode batchGeocode - processes array of locations", async () => {
624
+ const fastify = Fastify({ logger: false });
625
+ try {
626
+ await fastify.register(xGeocode, {
627
+ apiKey: "test-api-key"
628
+ });
629
+
630
+ // Mock fetch
631
+ const originalFetch = global.fetch;
632
+ global.fetch = async (url) => {
633
+ if (url.includes("10001")) {
634
+ return {
635
+ ok: true,
636
+ json: async () => ({
637
+ results: [{
638
+ location: { lat: 40.7506, lng: -73.9972 },
639
+ address_components: {
640
+ city: "New York",
641
+ county: "New York County",
642
+ state: "NY",
643
+ country: "US"
644
+ }
645
+ }]
646
+ })
647
+ };
648
+ }
649
+ return {
650
+ ok: true,
651
+ json: async () => ({
652
+ results: [{
653
+ location: { lat: 40.7128, lng: -74.0060 },
654
+ formatted_address: "123 Main St, New York, NY",
655
+ address_components: {
656
+ city: "New York",
657
+ county: "New York County",
658
+ state: "NY",
659
+ country: "US"
660
+ }
661
+ }]
662
+ })
663
+ };
664
+ };
665
+
666
+ try {
667
+ const result = await fastify.xGeocode.batchGeocode(["10001", "123 Main St"]);
668
+ assert.ok(Array.isArray(result), "Returns array");
669
+ assert.equal(result.length, 2, "Returns correct number of results");
670
+ assert.ok(result[0].lat, "First result has coordinates");
671
+ } finally {
672
+ global.fetch = originalFetch;
673
+ }
674
+ } finally {
675
+ await fastify.close();
676
+ }
677
+ });
678
+
679
+ test("xGeocode batchGeocode - rejects non-array input", async () => {
680
+ const fastify = Fastify({ logger: false });
681
+ try {
682
+ await fastify.register(xGeocode, {
683
+ apiKey: "test-api-key"
684
+ });
685
+
686
+ await assert.rejects(
687
+ () => fastify.xGeocode.batchGeocode("not an array"),
688
+ { message: /locations must be an array/ }
689
+ );
690
+ } finally {
691
+ await fastify.close();
692
+ }
693
+ });
694
+
695
+ test("xGeocode batchGeocode - rejects empty array", async () => {
696
+ const fastify = Fastify({ logger: false });
697
+ try {
698
+ await fastify.register(xGeocode, {
699
+ apiKey: "test-api-key"
700
+ });
701
+
702
+ await assert.rejects(
703
+ () => fastify.xGeocode.batchGeocode([]),
704
+ { message: /cannot be empty/ }
705
+ );
706
+ } finally {
707
+ await fastify.close();
708
+ }
709
+ });
710
+
711
+ test("xGeocode batchGeocode - rejects batch exceeding 100 locations", async () => {
712
+ const fastify = Fastify({ logger: false });
713
+ try {
714
+ await fastify.register(xGeocode, {
715
+ apiKey: "test-api-key"
716
+ });
717
+
718
+ const locations = Array(101).fill("10001");
719
+ await assert.rejects(
720
+ () => fastify.xGeocode.batchGeocode(locations),
721
+ { message: /cannot exceed 100/ }
722
+ );
723
+ } finally {
724
+ await fastify.close();
725
+ }
726
+ });
727
+
728
+ test("xGeocode batchGeocode - handles per-item errors gracefully", async () => {
729
+ const fastify = Fastify({ logger: false });
730
+ try {
731
+ await fastify.register(xGeocode, {
732
+ apiKey: "test-api-key"
733
+ });
734
+
735
+ // Mock fetch to fail for specific items
736
+ const originalFetch = global.fetch;
737
+ global.fetch = async (url) => {
738
+ if (url.includes("invalid")) {
739
+ return {
740
+ ok: true,
741
+ json: async () => ({ results: [] })
742
+ };
743
+ }
744
+ return {
745
+ ok: true,
746
+ json: async () => ({
747
+ results: [{
748
+ location: { lat: 40.7506, lng: -73.9972 },
749
+ address_components: {
750
+ city: "New York",
751
+ county: "New York County",
752
+ state: "NY",
753
+ country: "US"
754
+ }
755
+ }]
756
+ })
757
+ };
758
+ };
759
+
760
+ try {
761
+ const result = await fastify.xGeocode.batchGeocode(["10001", "invalid", "10001"]);
762
+ assert.ok(Array.isArray(result), "Returns array even with errors");
763
+ assert.equal(result.length, 3, "Returns result for all items");
764
+ assert.ok(result[0].lat, "Successful items have coordinates");
765
+ assert.equal(result[1].success, false, "Failed items marked with success: false");
766
+ assert.ok(result[1].error, "Failed items have error message");
767
+ } finally {
768
+ global.fetch = originalFetch;
769
+ }
770
+ } finally {
771
+ await fastify.close();
772
+ }
773
+ });
774
+
775
+ // validateAddress() Tests
776
+ test("xGeocode validateAddress - validates and returns formatted address", async () => {
777
+ const fastify = Fastify({ logger: false });
778
+ try {
779
+ await fastify.register(xGeocode, {
780
+ apiKey: "test-api-key"
781
+ });
782
+
783
+ // Mock fetch
784
+ const originalFetch = global.fetch;
785
+ global.fetch = async () => ({
786
+ ok: true,
787
+ json: async () => ({
788
+ results: [{
789
+ location: { lat: 40.7128, lng: -74.0060 },
790
+ formatted_address: "123 Main Street, New York, NY 10001, USA",
791
+ address_components: {
792
+ city: "New York",
793
+ county: "New York County",
794
+ state: "NY",
795
+ country: "US",
796
+ zip: "10001"
797
+ },
798
+ accuracy: "rooftop"
799
+ }]
800
+ })
801
+ });
802
+
803
+ try {
804
+ const result = await fastify.xGeocode.validateAddress("123 Main St");
805
+ assert.ok(result, "Returns validation result");
806
+ assert.equal(result.valid, true, "Marks valid address as valid");
807
+ assert.equal(result.input, "123 Main St", "Returns original input");
808
+ assert.ok(result.formatted, "Returns formatted address");
809
+ assert.ok(result.confidence, "Returns confidence/accuracy");
810
+ } finally {
811
+ global.fetch = originalFetch;
812
+ }
813
+ } finally {
814
+ await fastify.close();
815
+ }
816
+ });
817
+
818
+ test("xGeocode validateAddress - returns invalid for unrecognized address", async () => {
819
+ const fastify = Fastify({ logger: false });
820
+ try {
821
+ await fastify.register(xGeocode, {
822
+ apiKey: "test-api-key"
823
+ });
824
+
825
+ // Mock fetch to return empty results
826
+ const originalFetch = global.fetch;
827
+ global.fetch = async () => ({
828
+ ok: true,
829
+ json: async () => ({ results: [] })
830
+ });
831
+
832
+ try {
833
+ const result = await fastify.xGeocode.validateAddress("completely invalid address");
834
+ assert.equal(result.valid, false, "Marks unrecognized address as invalid");
835
+ assert.equal(result.input, "completely invalid address", "Returns original input");
836
+ assert.ok(result.error, "Returns error message");
837
+ } finally {
838
+ global.fetch = originalFetch;
839
+ }
840
+ } finally {
841
+ await fastify.close();
842
+ }
843
+ });
844
+
845
+ test("xGeocode validateAddress - handles API errors gracefully", async () => {
846
+ const fastify = Fastify({ logger: false });
847
+ try {
848
+ await fastify.register(xGeocode, {
849
+ apiKey: "test-api-key"
850
+ });
851
+
852
+ // Mock fetch to return error status
853
+ const originalFetch = global.fetch;
854
+ global.fetch = async () => ({
855
+ ok: false,
856
+ status: 500
857
+ });
858
+
859
+ try {
860
+ const result = await fastify.xGeocode.validateAddress("123 Main St");
861
+ assert.equal(result.valid, false, "Marks API errors as invalid");
862
+ assert.ok(result.error, "Returns error message");
863
+ } finally {
864
+ global.fetch = originalFetch;
865
+ }
866
+ } finally {
867
+ await fastify.close();
868
+ }
869
+ });
870
+
871
+ test("xGeocode validateAddress - rejects address with less than 3 characters", async () => {
872
+ const fastify = Fastify({ logger: false });
873
+ try {
874
+ await fastify.register(xGeocode, {
875
+ apiKey: "test-api-key"
876
+ });
877
+
878
+ await assert.rejects(
879
+ () => fastify.xGeocode.validateAddress("ab"),
880
+ { message: /minimum 3 characters/ }
881
+ );
882
+ } finally {
883
+ await fastify.close();
884
+ }
885
+ });
886
+
887
+ test("xGeocode validateAddress - rejects null address", async () => {
888
+ const fastify = Fastify({ logger: false });
889
+ try {
890
+ await fastify.register(xGeocode, {
891
+ apiKey: "test-api-key"
892
+ });
893
+
894
+ await assert.rejects(
895
+ () => fastify.xGeocode.validateAddress(null),
896
+ { message: /Invalid input/ }
897
+ );
898
+ } finally {
899
+ await fastify.close();
900
+ }
901
+ });