• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stores-com / bloodhound / 31017787024

05 Aug 2026 02:56PM UTC coverage: 88.0% (+0.5%) from 87.506%
31017787024

push

github

web-flow
Merge pull request #187 from stores-com/promise-carriers

Convert every carrier from callbacks to async/await

317 of 372 branches covered (85.22%)

Branch coverage included in aggregate %.

640 of 694 new or added lines in 11 files covered. (92.22%)

4 existing lines in 3 files now uncovered.

1487 of 1678 relevant lines covered (88.62%)

18.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.11
/carriers/fedEx.js
1
const async = require('async');
12✔
2
const cache = require('memory-cache');
12✔
3
const HttpError = require('@stores.com/http-error');
12✔
4

12✔
5
const checkDigit = require('../util/checkDigit');
12✔
6

12✔
7
// Remove these words from cities to turn cities like `FEDEX SMARTPOST INDIANAPOLIS` into `INDIANAPOLIS`
12✔
8
const CITY_BLACKLIST = /fedex|smartpost/ig;
12✔
9

12✔
10
// These tracking status codes indicate the shipment was delivered: https://www.fedex.com/us/developer/webhelp/ws/2018/US/index.htm#t=wsdvg%2FTracking_Shipments.htm%23Tracking_Statusbc-5&rhtocid=_26_0_4
12✔
11
const DELIVERED_TRACKING_STATUS_CODES = ['DL'];
12✔
12

12✔
13
// These tracking status codes indicate the shipment was shipped (shows movement beyond a shipping label being created): https://www.fedex.com/us/developer/webhelp/ws/2018/US/index.htm#t=wsdvg%2FTracking_Shipments.htm%23Tracking_Statusbc-5&rhtocid=_26_0_4
12✔
14
const SHIPPED_TRACKING_STATUS_CODES = ['AR', 'DP', 'IT', 'OD'];
12✔
15

12✔
16
// The events from these tracking status codes are filtered because their timestamps are nonsensical: https://www.fedex.com/us/developer/webhelp/ws/2018/US/index.htm#t=wsdvg%2FTracking_Shipments.htm%23Tracking_Statusbc-5&rhtocid=_26_0_4
12✔
17
const TRACKING_STATUS_CODES_BLACKLIST = ['PU', 'PX'];
12✔
18

12✔
19
function FedEx(args) {
55✔
20
    const options = Object.assign({
55✔
21
        url: 'https://apis.fedex.com'
55✔
22
    }, args);
55✔
23

55✔
24
    this.getAccessToken = async () => {
55✔
25
        const key = args.api_key;
30✔
26
        const cachedAccessToken = cache.get(key);
30✔
27

30✔
28
        if (cachedAccessToken) {
30✔
29
            return cachedAccessToken;
1✔
30
        }
1✔
31

1✔
32
        // URLSearchParams as the body reproduces request's `form` option, including
1✔
33
        // the application/x-www-form-urlencoded content type.
1✔
34
        const response = await fetch(`${options.url}/oauth/token`, {
1✔
35
            body: new URLSearchParams({
1✔
36
                client_id: args.api_key,
1✔
37
                client_secret: args.secret_key,
1✔
38
                grant_type: 'client_credentials'
1✔
39
            }),
1✔
40
            method: 'POST',
1✔
41
            signal: AbortSignal.timeout(30000)
1✔
42
        });
1✔
43

1✔
44
        if (response.status !== 200) {
30✔
45
            throw await HttpError.from(response);
1✔
46
        }
1✔
47

6✔
48
        const accessToken = await response.json();
6✔
49

6✔
50
        cache.put(key, accessToken, (accessToken.expires_in - 100) * 1000);
6✔
51

6✔
52
        return accessToken;
6✔
53
    };
55✔
54

55✔
55
    this.isTrackingNumberValid = (trackingNumber) => {
55✔
56
        // Remove whitespace
20✔
57
        trackingNumber = trackingNumber.replace(/\s/g, '');
20✔
58

20✔
59
        if ([/^6129\d{16}$/, /^7489\d{16}$/, /^926129\d{16}$/, /^927489\d{16}$/].some(regex => regex.test(trackingNumber))) {
20✔
60
            return true;
6✔
61
        }
6✔
62

14✔
63
        if (/^02\d{18}$/.test(trackingNumber)) {
20✔
64
            return checkDigit(`91${trackingNumber}`, [3, 1], 10);
1✔
65
        }
1✔
66

13✔
67
        if (/^96\d{20}$/.test(trackingNumber)) {
20✔
68
            if (checkDigit(trackingNumber, [3, 1, 7], 11)) {
2✔
69
                return true;
1✔
70
            }
1✔
71

1✔
72
            if (checkDigit(trackingNumber.slice(7), [1, 3], 10)) {
1✔
73
                return true;
1✔
74
            }
1✔
75

2✔
76
            return false;
2✔
77
        }
2✔
78

11✔
79
        if (/^DT\d{12}$/.test(trackingNumber)) {
20✔
80
            return checkDigit(trackingNumber.match(/^DT(\d{12})$/)[1], [3, 1, 7], 11);
1✔
81
        }
1✔
82

10✔
83
        if (/^\d{12}$/.test(trackingNumber)) {
20✔
84
            return checkDigit(trackingNumber, [3, 1, 7], 11);
2✔
85
        }
2✔
86

8✔
87
        if (/^\d{15}$/.test(trackingNumber)) {
20✔
88
            return checkDigit(trackingNumber, [1, 3], 10);
3✔
89
        }
3✔
90

4✔
91
        if (/^\d{20}$/.test(trackingNumber)) {
1✔
92
            if (checkDigit(trackingNumber, [3, 1, 7], 11)) {
1!
93
                return true;
×
94
            }
×
95

1✔
96
            if (checkDigit(`92${trackingNumber}`, [3, 1], 10)) {
1✔
97
                return true;
1✔
98
            }
1!
99

×
100
            return false;
×
101
        }
✔
102

4✔
103
        return false;
4✔
104
    };
55✔
105

55✔
106
    this.track = async (trackingNumber, _options = {}) => {
55✔
107
        if (!_options.minDate) {
30✔
108
            _options.minDate = new Date(0);
30✔
109
        }
30✔
110

30✔
111
        const accessToken = await this.getAccessToken();
30✔
112

27✔
113
        const trackReply = await async.retry(async () => {
27✔
114
            const response = await fetch(`${options.url}/track/v1/trackingnumbers`, {
33✔
115
                body: JSON.stringify({
33✔
116
                    includeDetailedScans: true,
33✔
117
                    trackingInfo: [
33✔
118
                        {
33✔
119
                            trackingNumberInfo: {
33✔
120
                                trackingNumber: trackingNumber
33✔
121
                            }
33✔
122
                        }
33✔
123
                    ]
33✔
124
                }),
33✔
125
                headers: {
33✔
126
                    'Accept-Encoding': 'gzip, deflate',
33✔
127
                    'Authorization': `Bearer ${accessToken.access_token}`,
33✔
128
                    'Content-Type': 'application/json'
33✔
129
                },
33✔
130
                method: 'POST',
33✔
131
                signal: AbortSignal.timeout(30000)
33✔
132
            });
33✔
133

33✔
134
            const text = await response.text();
33✔
135
            let trackResponse;
33✔
136

33✔
137
            try {
33✔
138
                trackResponse = JSON.parse(text);
33✔
139
            } catch {
33!
NEW
140
                // request() handed a non-JSON error page back as a raw string, which
×
NEW
141
                // then fell through as a success with no events. Surface it instead.
×
NEW
142
                throw await HttpError.from(response);
×
NEW
143
            }
×
144

33✔
145
            // FedEx reports per-shipment problems in output.alerts on an otherwise
33✔
146
            // successful response, and those messages say more than the status line,
33✔
147
            // so they're checked before the status.
33✔
148
            if (trackResponse?.output?.alerts?.length) {
33✔
149
                const warnings = trackResponse.output.alerts.filter(alert => alert.alertType === 'WARNING');
27✔
150

27✔
151
                if (warnings.length) {
27✔
152
                    throw new Error(warnings.map(warning => `${warning.code}: ${warning.message}`).join(', '));
5✔
153
                }
5✔
154
            }
27✔
155

26✔
156
            if (!response.ok) {
33!
NEW
157
                throw await HttpError.from(response, trackResponse);
×
NEW
158
            }
✔
159

26✔
160
            return trackResponse;
26✔
161
        });
27✔
162

26✔
163
        const results = {
26✔
164
            carrier: 'FedEx',
26✔
165
            events: [],
26✔
166
            raw: trackReply
26✔
167
        };
26✔
168
        // Extract the first tracking result from a nested FedEx tracking respons
26✔
169
        const trackResult = trackReply?.output?.completeTrackResults?.[0]?.trackResults?.[0];
30✔
170
        // Extract estimated delivery window if available
30✔
171
        const timeWindow = trackResult?.estimatedDeliveryTimeWindow;
30✔
172

30✔
173
        // Check if a time window is available
30✔
174
        if (timeWindow?.window?.begins && timeWindow?.window?.ends) {
30✔
175
            results.estimatedDeliveryDate = {
1✔
176
                earliest: new Date(timeWindow.window.begins),
1✔
177
                latest: new Date(timeWindow.window.ends)
1✔
178
            };
1✔
179
        }
1✔
180

26✔
181
        // Ensure track reply has events
26✔
182
        if (!trackReply?.output?.completeTrackResults[0]?.trackResults[0]?.scanEvents?.length) {
30✔
183
            return results;
4✔
184
        }
4✔
185
        trackReply?.output?.completeTrackResults?.[0]?.trackResults?.[0]?.scanEvents.forEach(e => {
30✔
186
            if (TRACKING_STATUS_CODES_BLACKLIST.includes(e.eventType)) {
162✔
187
                return;
15✔
188
            }
15✔
189

145✔
190
            const event = {
145✔
191
                address: {
145✔
192
                    city: e?.scanLocation?.city,
162✔
193
                    country: e?.scanLocation?.countryCode,
162✔
194
                    state: e?.scanLocation?.stateOrProvinceCode,
162✔
195
                    zip: e?.scanLocation?.postalCode
162✔
196
                },
162✔
197
                date: new Date(e.date),
162✔
198
                description: e.eventDescription
162✔
199
            };
162✔
200

162✔
201
            // Ensure event is after minDate (used to prevent data from reused tracking numbers)
162✔
202
            if (event.date < _options.minDate) {
162!
NEW
203
                return;
×
NEW
204
            }
✔
205

145✔
206
            if (e.exceptionDescription) {
162✔
207
                event.details = e.exceptionDescription;
25✔
208
            }
25✔
209

145✔
210
            // Remove blacklisted words
145✔
211
            if (event.address.city) {
162✔
212
                event.address.city = event.address.city.replace(CITY_BLACKLIST, '').trim();
122✔
213
            }
122✔
214

145✔
215
            if (DELIVERED_TRACKING_STATUS_CODES.includes(e.eventType)) {
162✔
216
                results.deliveredAt = new Date(e.date);
2✔
217
            }
2✔
218

145✔
219
            if (SHIPPED_TRACKING_STATUS_CODES.includes(e.eventType)) {
162✔
220
                results.shippedAt = new Date(e.date);
102✔
221
            }
102✔
222

145✔
223
            results.events.push(event);
145✔
224
        });
30✔
225
        // Add url to carrier tracking page
30✔
226
        results.url = `https://www.fedex.com/apps/fedextrack/?tracknumbers=${encodeURIComponent(trackingNumber)}`;
30✔
227

30✔
228
        if (!results.shippedAt && results.deliveredAt) {
30!
NEW
229
            results.shippedAt = results.deliveredAt;
×
NEW
230
        }
✔
231

22✔
232
        return results;
22✔
233
    };
55✔
234
}
55✔
235

12✔
236
module.exports = FedEx;
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc