From 1810245f232073f3dfb8e4f461828312d160810f Mon Sep 17 00:00:00 2001 From: "Farhat R. Kabir" <148591416+farhatraiyan@users.noreply.github.com> Date: Thu, 21 May 2026 10:22:19 -0500 Subject: [PATCH 1/3] Add track(trackRequest, options) for FedEx Track API v0.5.0 Calls POST /track/v1/trackingnumbers (Basic Integrated Visibility). Same passthrough pattern as existing methods. Track API requires separate FedEx project credentials (FEDEX_TRACK_API_KEY/SECRET_KEY). Co-Authored-By: Claude Opus 4.6 --- .github/workflows/test.yml | 2 + CHANGELOG.md | 4 + README.md | 26 +- index.js | 45 ++++ package.json | 8 +- test/index.js | 482 +++++++++++++++++++++++++++++++++++++ 6 files changed, 563 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80d96f6..c5392ae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,8 @@ jobs: FEDEX_API_KEY: ${{ secrets.FEDEX_API_KEY }} FEDEX_SECRET_KEY: ${{ secrets.FEDEX_SECRET_KEY }} FEDEX_SMART_POST_HUB_ID: ${{ secrets.FEDEX_SMART_POST_HUB_ID }} + FEDEX_TRACK_API_KEY: ${{ secrets.FEDEX_TRACK_API_KEY }} + FEDEX_TRACK_SECRET_KEY: ${{ secrets.FEDEX_TRACK_SECRET_KEY }} FEDEX_URL: ${{ secrets.FEDEX_URL }} run: | node --test --test-force-exit --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=lcov.info diff --git a/CHANGELOG.md b/CHANGELOG.md index 834b8d3..39d7a68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.5.0 + +- Add `track(trackRequest, options)` — calls the FedEx Track API (`POST /track/v1/trackingnumbers`). Same passthrough pattern as the other methods: caller supplies the full request body, the package forwards it verbatim. Supports `options.customer_transaction_id` and `options.timeout`. Non-2xx responses and 200-with-`errors[]` envelopes both reject with `HttpError`. + ## 0.4.0 - Add `groundEndOfDayClose(closeRequest, options)` — calls the FedEx Ground End of Day Close API (`PUT /ship/v1/endofday/`). Same passthrough pattern as the other methods: caller supplies the full request body, the package forwards it verbatim. Supports `options.customer_transaction_id` and `options.timeout`. Non-2xx responses and 200-with-`errors[]` envelopes both reject with `HttpError`. diff --git a/README.md b/README.md index 57f31aa..49e97ee 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![npm version](https://img.shields.io/npm/v/@stores.com/fedex)](https://www.npmjs.com/package/@stores.com/fedex) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -FedEx REST API client for Address Validation, Ground End of Day Close, OAuth tokens, Rates and Transit Times, Shipment Cancellation, and Shipment Creation. +FedEx REST API client for Address Validation, Ground End of Day Close, OAuth tokens, Rates and Transit Times, Shipment Cancellation, Shipment Creation, and Tracking. ## Installation @@ -181,6 +181,30 @@ console.log(detail.totalNetCharge); Non-2xx responses reject with `HttpError`. If FedEx returns a 200 response carrying a non-empty `errors[]` envelope, the call rejects with an `HttpError` whose message is every `message` joined by `; ` and whose `.json` is the full response body (with the `errors[]` array, codes, and any other fields). +### track(trackRequest, options) + +Track a FedEx shipment via the Track API. The caller supplies the full request body — `includeDetailedScans`, `trackingInfo` — and the package forwards it verbatim. + +See: https://developer.fedex.com/api/en-us/catalog/track/v1/docs.html + +```javascript +const json = await fedex.track({ + includeDetailedScans: true, + trackingInfo: [{ + trackingNumberInfo: { + trackingNumber: '794644790138' + } + }] +}); + +const trackResult = json.output.completeTrackResults[0].trackResults[0]; + +console.log(trackResult.latestStatusDetail.statusByLocale); +// 'Delivered' +``` + +Non-2xx responses reject with `HttpError`. If FedEx returns a 200 response carrying a non-empty `errors[]` envelope, the call rejects with an `HttpError` whose message is every `message` joined by `; ` and whose `.json` is the full response body (with the `errors[]` array, codes, and any other fields). + ### validateAddress(addressValidationRequest, options) Validate and resolve addresses using the FedEx Address Validation API. The caller supplies the full request body — `addressesToValidate` and optionally `inEffectAsOfTimestamp` — and the package forwards it verbatim. diff --git a/index.js b/index.js index aaea905..fa1e112 100644 --- a/index.js +++ b/index.js @@ -230,6 +230,51 @@ function FedEx(args) { return json; }; + /** + * Track a FedEx shipment via the Track API. The caller supplies the full request + * body — `includeDetailedScans`, `trackingInfo` — and the package forwards it + * verbatim. + * + * @param {object} trackRequest - Full Track by Tracking Number request body. + * @param {object} [options] + * @param {string} [options.customer_transaction_id] - Sent as the `x-customer-transaction-id` + * request header. FedEx echoes this back so callers can correlate requests with responses. + * @param {number} [options.timeout=30000] - Request timeout in milliseconds. + * @returns {Promise} The parsed response body, including `output.completeTrackResults[]`. + * @see https://developer.fedex.com/api/en-us/catalog/track/v1/docs.html + */ + this.track = async (trackRequest, options = {}) => { + const accessToken = await this.getAccessToken(); + + const headers = { + Authorization: `Bearer ${accessToken.access_token}`, + 'Content-Type': 'application/json' + }; + + if (options.customer_transaction_id) { + headers['x-customer-transaction-id'] = options.customer_transaction_id; + } + + const response = await fetch(`${_options.url}/track/v1/trackingnumbers`, { + body: JSON.stringify(trackRequest), + headers, + method: 'POST', + signal: AbortSignal.timeout(options.timeout || 30000) + }); + + if (!response.ok) { + throw await HttpError.from(response); + } + + const json = await response.json(); + + if (json.errors?.length) { + throw await HttpError.from(response); + } + + return json; + }; + /** * Call the FedEx Address Validation API. The caller supplies the full request body * — `addressesToValidate` and optionally `inEffectAsOfTimestamp` — and the package diff --git a/package.json b/package.json index a612a8a..85653e9 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "@stores.com/http-error": "~1.1.0", "memory-cache": "~0.2.0" }, - "description": "FedEx REST API client for address validation, ground end of day close, OAuth tokens, rate quotes, shipment cancellation, and shipment creation.", + "description": "FedEx REST API client for address validation, ground end of day close, OAuth tokens, rate quotes, shipment cancellation, shipment creation, and tracking.", "devDependencies": { "@eslint/js": "*", "async": "~3.2.0", @@ -18,7 +18,9 @@ "logistics", "rates", "ship", - "shipping" + "shipping", + "track", + "tracking" ], "license": "MIT", "name": "@stores.com/fedex", @@ -34,5 +36,5 @@ "test": "node --test --test-force-exit --test-reporter=spec", "test:only": "node --test --test-force-exit --test-only --test-reporter=spec" }, - "version": "0.4.0" + "version": "0.5.0" } diff --git a/test/index.js b/test/index.js index 4a906a6..10cd66d 100644 --- a/test/index.js +++ b/test/index.js @@ -905,6 +905,488 @@ test('rateAndTransitTimes (mocked)', async (t) => { }); }); +test('track', { concurrency: true }, async (t) => { + t.test('should track a shipment by tracking number', async () => { + const fedex = new FedEx({ + api_key: process.env.FEDEX_TRACK_API_KEY, + secret_key: process.env.FEDEX_TRACK_SECRET_KEY, + url: process.env.FEDEX_URL + }); + + const body = await async.retry(async () => fedex.track({ + includeDetailedScans: true, + trackingInfo: [{ + trackingNumberInfo: { + trackingNumber: '613746411451' + } + }] + })); + + assert(body); + assert(body.transactionId); + assert(body.output); + assert(Array.isArray(body.output.completeTrackResults)); + + const trackResult = body.output.completeTrackResults[0].trackResults[0]; + + assert(trackResult.serviceDetail.type); + assert(trackResult.packageDetails.weightAndDimensions.weight[0].value); + }); +}); + +test('track (mocked)', async (t) => { + t.test('should return weight, dimensions, and service type for a delivered Ground shipment', async (t) => { + t.mock.method(globalThis, 'fetch', async (url) => { + if (url.endsWith('/oauth/token')) { + return new Response(JSON.stringify({ access_token: 'mock', expires_in: 3600, token_type: 'bearer' }), { + headers: { 'Content-Type': 'application/json' }, + status: 200 + }); + } + + if (url.endsWith('/track/v1/trackingnumbers')) { + return new Response(JSON.stringify({ + transactionId: 'b450c79e-f286-4ff0-b04c-3a5b1149a923', + output: { + completeTrackResults: [ + { + trackingNumber: '794644790138', + trackResults: [ + { + trackingNumberInfo: { + trackingNumber: '794644790138', + carrierCode: 'FDXG', + trackingNumberUniqueId: '2459622000~794644790138~FX' + }, + additionalTrackingInfo: { + hasAssociatedShipments: false, + nickname: '', + packageIdentifiers: [ + { + type: 'TRACKING_NUMBER_OR_DOORTAG', + values: ['794644790138'], + trackingNumberUniqueId: '2459622000~794644790138~FX' + } + ] + }, + shipperInformation: { + address: { + city: 'MEMPHIS', + countryCode: 'US', + countryName: 'United States', + stateOrProvinceCode: 'TN' + } + }, + recipientInformation: { + address: { + city: 'NEW YORK', + countryCode: 'US', + countryName: 'United States', + stateOrProvinceCode: 'NY' + } + }, + latestStatusDetail: { + code: 'DL', + derivedCode: 'DL', + statusByLocale: 'Delivered', + description: 'Delivered', + scanLocation: { + city: 'NEW YORK', + countryCode: 'US', + stateOrProvinceCode: 'NY' + } + }, + dateAndTimes: [ + { + dateTime: '2026-05-14T10:25:00-04:00', + type: 'ACTUAL_DELIVERY' + }, + { + dateTime: '2026-05-10T00:00:00-05:00', + type: 'SHIP' + } + ], + packageDetails: { + packagingDescription: { + description: 'YOUR_PACKAGING', + type: 'YOUR_PACKAGING' + }, + physicalPackagingType: 'YOUR_PACKAGING', + sequenceNumber: '1', + count: '1', + weightAndDimensions: { + weight: [ + { + unit: 'LB', + value: '5.0' + }, + { + unit: 'KG', + value: '2.27' + } + ], + dimensions: [ + { + height: 8, + length: 12, + units: 'IN', + width: 10 + } + ] + } + }, + serviceDetail: { + description: 'FedEx Ground', + shortDescription: 'FG', + type: 'FEDEX_GROUND' + }, + scanEvents: [ + { + date: '2026-05-14T10:25:00-04:00', + derivedStatus: 'Delivered', + eventDescription: 'Delivered', + eventType: 'DL', + exceptionDescription: '', + scanLocation: { + city: 'NEW YORK', + countryCode: 'US', + postalCode: '10001', + stateOrProvinceCode: 'NY' + } + }, + { + date: '2026-05-14T06:12:00-04:00', + derivedStatus: 'On FedEx vehicle for delivery', + eventDescription: 'On FedEx vehicle for delivery', + eventType: 'OD', + exceptionDescription: '', + scanLocation: { + city: 'NEW YORK', + countryCode: 'US', + postalCode: '10001', + stateOrProvinceCode: 'NY' + } + }, + { + date: '2026-05-10T18:30:00-05:00', + derivedStatus: 'Picked up', + eventDescription: 'Picked up', + eventType: 'PU', + exceptionDescription: '', + scanLocation: { + city: 'MEMPHIS', + countryCode: 'US', + postalCode: '38116', + stateOrProvinceCode: 'TN' + } + } + ] + } + ] + } + ] + } + }), { + headers: { 'Content-Type': 'application/json' }, + status: 200 + }); + } + + throw new Error(`Unexpected fetch URL: ${url}`); + }); + + const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + + const body = await fedex.track({ + includeDetailedScans: true, + trackingInfo: [{ + trackingNumberInfo: { + trackingNumber: '794644790138' + } + }] + }); + + const trackResult = body.output.completeTrackResults[0].trackResults[0]; + + assert.strictEqual(trackResult.serviceDetail.type, 'FEDEX_GROUND'); + assert.strictEqual(trackResult.packageDetails.weightAndDimensions.weight[0].value, '5.0'); + assert.strictEqual(trackResult.packageDetails.weightAndDimensions.weight[0].unit, 'LB'); + assert.strictEqual(trackResult.packageDetails.weightAndDimensions.dimensions[0].height, 8); + assert.strictEqual(trackResult.packageDetails.weightAndDimensions.dimensions[0].length, 12); + assert.strictEqual(trackResult.packageDetails.weightAndDimensions.dimensions[0].width, 10); + assert.strictEqual(trackResult.latestStatusDetail.code, 'DL'); + }); + + t.test('should return weight without dimensions for an in-transit shipment', async (t) => { + t.mock.method(globalThis, 'fetch', async (url) => { + if (url.endsWith('/oauth/token')) { + return new Response(JSON.stringify({ access_token: 'mock', expires_in: 3600, token_type: 'bearer' }), { + headers: { 'Content-Type': 'application/json' }, + status: 200 + }); + } + + if (url.endsWith('/track/v1/trackingnumbers')) { + return new Response(JSON.stringify({ + transactionId: '7e1a3b5c-8d2f-4e6a-9c0b-1f2a3b4c5d6e', + output: { + completeTrackResults: [ + { + trackingNumber: '61292701107834445671', + trackResults: [ + { + trackingNumberInfo: { + trackingNumber: '61292701107834445671', + carrierCode: 'FDXG', + trackingNumberUniqueId: '2459674000~61292701107834445671~FX' + }, + additionalTrackingInfo: { + hasAssociatedShipments: false, + nickname: '', + packageIdentifiers: [ + { + type: 'TRACKING_NUMBER_OR_DOORTAG', + values: ['61292701107834445671'], + trackingNumberUniqueId: '2459674000~61292701107834445671~FX' + } + ] + }, + shipperInformation: { + address: { + city: 'LINDON', + countryCode: 'US', + countryName: 'United States', + stateOrProvinceCode: 'UT' + } + }, + recipientInformation: { + address: { + city: 'CHICAGO', + countryCode: 'US', + countryName: 'United States', + stateOrProvinceCode: 'IL' + } + }, + latestStatusDetail: { + code: 'IT', + derivedCode: 'IT', + statusByLocale: 'In transit', + description: 'In transit', + scanLocation: { + city: 'KANSAS CITY', + countryCode: 'US', + stateOrProvinceCode: 'MO' + } + }, + dateAndTimes: [ + { + dateTime: '2026-05-16T23:59:59-05:00', + type: 'ESTIMATED_DELIVERY' + }, + { + dateTime: '2026-05-13T00:00:00-06:00', + type: 'SHIP' + } + ], + packageDetails: { + packagingDescription: { + description: 'YOUR_PACKAGING', + type: 'YOUR_PACKAGING' + }, + physicalPackagingType: 'YOUR_PACKAGING', + sequenceNumber: '1', + count: '1', + weightAndDimensions: { + weight: [ + { + unit: 'LB', + value: '0.60' + }, + { + unit: 'KG', + value: '0.27' + } + ] + } + }, + serviceDetail: { + description: 'FedEx Ground Economy', + shortDescription: 'SP', + type: 'SMART_POST' + }, + scanEvents: [ + { + date: '2026-05-14T14:30:00-05:00', + derivedStatus: 'In transit', + eventDescription: 'In transit', + eventType: 'IT', + exceptionDescription: '', + scanLocation: { + city: 'KANSAS CITY', + countryCode: 'US', + postalCode: '64161', + stateOrProvinceCode: 'MO' + } + }, + { + date: '2026-05-13T18:45:00-06:00', + derivedStatus: 'Picked up', + eventDescription: 'Picked up', + eventType: 'PU', + exceptionDescription: '', + scanLocation: { + city: 'LINDON', + countryCode: 'US', + postalCode: '84042', + stateOrProvinceCode: 'UT' + } + } + ] + } + ] + } + ] + } + }), { + headers: { 'Content-Type': 'application/json' }, + status: 200 + }); + } + + throw new Error(`Unexpected fetch URL: ${url}`); + }); + + const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + + const body = await fedex.track({ + includeDetailedScans: true, + trackingInfo: [{ + trackingNumberInfo: { + trackingNumber: '61292701107834445671' + } + }] + }); + + const trackResult = body.output.completeTrackResults[0].trackResults[0]; + + assert.strictEqual(trackResult.serviceDetail.type, 'SMART_POST'); + assert.strictEqual(trackResult.packageDetails.weightAndDimensions.weight[0].value, '0.60'); + assert.strictEqual(trackResult.packageDetails.weightAndDimensions.weight[0].unit, 'LB'); + assert.strictEqual(trackResult.packageDetails.weightAndDimensions.dimensions, undefined); + assert.strictEqual(trackResult.latestStatusDetail.code, 'IT'); + }); + + t.test('should send options.customer_transaction_id as x-customer-transaction-id header and use POST method', async (t) => { + let sentHeader; + let sentMethod; + + t.mock.method(globalThis, 'fetch', async (url, init) => { + if (url.endsWith('/oauth/token')) { + return new Response(JSON.stringify({ access_token: 'mock', expires_in: 3600, token_type: 'bearer' }), { + headers: { 'Content-Type': 'application/json' }, + status: 200 + }); + } + + if (url.endsWith('/track/v1/trackingnumbers')) { + sentHeader = init.headers['x-customer-transaction-id']; + sentMethod = init.method; + return new Response(JSON.stringify({ output: { completeTrackResults: [] }, transactionId: 'mock' }), { + headers: { 'Content-Type': 'application/json' }, + status: 200 + }); + } + + throw new Error(`Unexpected fetch URL: ${url}`); + }); + + const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + + await fedex.track({ + includeDetailedScans: true, + trackingInfo: [{ + trackingNumberInfo: { + trackingNumber: '794644790138' + } + }] + }, { customer_transaction_id: 'abc-123' }); + + assert.strictEqual(sentHeader, 'abc-123'); + assert.strictEqual(sentMethod, 'POST'); + }); + + t.test('should throw HttpError for 200 response with errors envelope', async (t) => { + t.mock.method(globalThis, 'fetch', async (url) => { + if (url.endsWith('/oauth/token')) { + return new Response(JSON.stringify({ access_token: 'mock', expires_in: 3600, token_type: 'bearer' }), { + headers: { 'Content-Type': 'application/json' }, + status: 200 + }); + } + + if (url.endsWith('/track/v1/trackingnumbers')) { + return new Response(JSON.stringify({ + errors: [ + { code: 'TRACKING.TCNNOTFOUND', message: 'Tracking number cannot be found' } + ], + transactionId: 'mock' + }), { + headers: { 'Content-Type': 'application/json' }, + status: 200 + }); + } + + throw new Error(`Unexpected fetch URL: ${url}`); + }); + + const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + + await assert.rejects(fedex.track({ + includeDetailedScans: true, + trackingInfo: [{ + trackingNumberInfo: { + trackingNumber: '000000000000' + } + }] + }), (err) => { + assert.strictEqual(err.name, 'HttpError'); + return true; + }); + }); + + t.test('should throw HttpError for non 2xx response', async (t) => { + t.mock.method(globalThis, 'fetch', async (url) => { + if (url.endsWith('/oauth/token')) { + return new Response(JSON.stringify({ access_token: 'mock', expires_in: 3600, token_type: 'bearer' }), { + headers: { 'Content-Type': 'application/json' }, + status: 200 + }); + } + + if (url.endsWith('/track/v1/trackingnumbers')) { + return new Response('', { status: 500, statusText: 'Internal Server Error' }); + } + + throw new Error(`Unexpected fetch URL: ${url}`); + }); + + const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + + await assert.rejects(fedex.track({ + includeDetailedScans: true, + trackingInfo: [{ + trackingNumberInfo: { + trackingNumber: '794644790138' + } + }] + }), (err) => { + assert.strictEqual(err.name, 'HttpError'); + assert.match(err.message, /^500/); + return true; + }); + }); +}); + test('validateAddress', { concurrency: true }, async (t) => { t.test('should return resolved addresses', async () => { const fedex = new FedEx({ From 7d64d5d7e74047c9c94db6035f6848d930ef2b5f Mon Sep 17 00:00:00 2001 From: "Farhat R. Kabir" <148591416+farhatraiyan@users.noreply.github.com> Date: Thu, 21 May 2026 10:36:01 -0500 Subject: [PATCH 2/3] Use fedEx variable casing in track tests Co-Authored-By: Claude Opus 4.6 --- test/index.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/index.js b/test/index.js index 4a74100..e727567 100644 --- a/test/index.js +++ b/test/index.js @@ -907,13 +907,13 @@ test('rateAndTransitTimes (mocked)', async (t) => { test('track', { concurrency: true }, async (t) => { t.test('should track a shipment by tracking number', async () => { - const fedex = new FedEx({ + const fedEx = new FedEx({ api_key: process.env.FEDEX_TRACK_API_KEY, secret_key: process.env.FEDEX_TRACK_SECRET_KEY, url: process.env.FEDEX_URL }); - const body = await async.retry(async () => fedex.track({ + const body = await async.retry(async () => fedEx.track({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -1095,9 +1095,9 @@ test('track (mocked)', async (t) => { throw new Error(`Unexpected fetch URL: ${url}`); }); - const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - const body = await fedex.track({ + const body = await fedEx.track({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -1256,9 +1256,9 @@ test('track (mocked)', async (t) => { throw new Error(`Unexpected fetch URL: ${url}`); }); - const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - const body = await fedex.track({ + const body = await fedEx.track({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -1300,9 +1300,9 @@ test('track (mocked)', async (t) => { throw new Error(`Unexpected fetch URL: ${url}`); }); - const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - await fedex.track({ + await fedEx.track({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -1339,9 +1339,9 @@ test('track (mocked)', async (t) => { throw new Error(`Unexpected fetch URL: ${url}`); }); - const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - await assert.rejects(fedex.track({ + await assert.rejects(fedEx.track({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -1370,9 +1370,9 @@ test('track (mocked)', async (t) => { throw new Error(`Unexpected fetch URL: ${url}`); }); - const fedex = new FedEx({ api_key: 'mock', secret_key: 'mock' }); + const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - await assert.rejects(fedex.track({ + await assert.rejects(fedEx.track({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { From a12158b535d8d7168b3741e7ee83dc56e4815e89 Mon Sep 17 00:00:00 2001 From: Shawn Miller Date: Thu, 21 May 2026 11:12:29 -0500 Subject: [PATCH 3/3] Rename track to trackByTrackingNumber Match the FedEx API operation name "Track by Tracking Number". https://developer.fedex.com/wirc/browser/#operation/Track%20by%20Tracking%20Number Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 2 +- README.md | 4 ++-- index.js | 2 +- test/index.js | 16 ++++++++-------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39d7a68..67b482c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 0.5.0 -- Add `track(trackRequest, options)` — calls the FedEx Track API (`POST /track/v1/trackingnumbers`). Same passthrough pattern as the other methods: caller supplies the full request body, the package forwards it verbatim. Supports `options.customer_transaction_id` and `options.timeout`. Non-2xx responses and 200-with-`errors[]` envelopes both reject with `HttpError`. +- Add `trackByTrackingNumber(trackRequest, options)` — calls the FedEx Track API (`POST /track/v1/trackingnumbers`). Same passthrough pattern as the other methods: caller supplies the full request body, the package forwards it verbatim. Supports `options.customer_transaction_id` and `options.timeout`. Non-2xx responses and 200-with-`errors[]` envelopes both reject with `HttpError`. ## 0.4.0 diff --git a/README.md b/README.md index 4d89005..858d353 100644 --- a/README.md +++ b/README.md @@ -181,14 +181,14 @@ console.log(detail.totalNetCharge); Non-2xx responses reject with `HttpError`. If FedEx returns a 200 response carrying a non-empty `errors[]` envelope, the call rejects with an `HttpError` whose message is every `message` joined by `; ` and whose `.json` is the full response body (with the `errors[]` array, codes, and any other fields). -### track(trackRequest, options) +### trackByTrackingNumber(trackRequest, options) Track a FedEx shipment via the Track API. The caller supplies the full request body — `includeDetailedScans`, `trackingInfo` — and the package forwards it verbatim. See: https://developer.fedex.com/api/en-us/catalog/track/v1/docs.html ```javascript -const json = await fedex.track({ +const json = await fedEx.trackByTrackingNumber({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { diff --git a/index.js b/index.js index fa1e112..adfcbcd 100644 --- a/index.js +++ b/index.js @@ -243,7 +243,7 @@ function FedEx(args) { * @returns {Promise} The parsed response body, including `output.completeTrackResults[]`. * @see https://developer.fedex.com/api/en-us/catalog/track/v1/docs.html */ - this.track = async (trackRequest, options = {}) => { + this.trackByTrackingNumber = async (trackRequest, options = {}) => { const accessToken = await this.getAccessToken(); const headers = { diff --git a/test/index.js b/test/index.js index e727567..774c3d0 100644 --- a/test/index.js +++ b/test/index.js @@ -905,7 +905,7 @@ test('rateAndTransitTimes (mocked)', async (t) => { }); }); -test('track', { concurrency: true }, async (t) => { +test('trackByTrackingNumber', { concurrency: true }, async (t) => { t.test('should track a shipment by tracking number', async () => { const fedEx = new FedEx({ api_key: process.env.FEDEX_TRACK_API_KEY, @@ -913,7 +913,7 @@ test('track', { concurrency: true }, async (t) => { url: process.env.FEDEX_URL }); - const body = await async.retry(async () => fedEx.track({ + const body = await async.retry(async () => fedEx.trackByTrackingNumber({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -934,7 +934,7 @@ test('track', { concurrency: true }, async (t) => { }); }); -test('track (mocked)', async (t) => { +test('trackByTrackingNumber (mocked)', async (t) => { t.test('should return weight, dimensions, and service type for a delivered Ground shipment', async (t) => { t.mock.method(globalThis, 'fetch', async (url) => { if (url.endsWith('/oauth/token')) { @@ -1097,7 +1097,7 @@ test('track (mocked)', async (t) => { const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - const body = await fedEx.track({ + const body = await fedEx.trackByTrackingNumber({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -1258,7 +1258,7 @@ test('track (mocked)', async (t) => { const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - const body = await fedEx.track({ + const body = await fedEx.trackByTrackingNumber({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -1302,7 +1302,7 @@ test('track (mocked)', async (t) => { const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - await fedEx.track({ + await fedEx.trackByTrackingNumber({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -1341,7 +1341,7 @@ test('track (mocked)', async (t) => { const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - await assert.rejects(fedEx.track({ + await assert.rejects(fedEx.trackByTrackingNumber({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: { @@ -1372,7 +1372,7 @@ test('track (mocked)', async (t) => { const fedEx = new FedEx({ api_key: 'mock', secret_key: 'mock' }); - await assert.rejects(fedEx.track({ + await assert.rejects(fedEx.trackByTrackingNumber({ includeDetailedScans: true, trackingInfo: [{ trackingNumberInfo: {