Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 93 additions & 15 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,64 @@
const assert = require('node:assert');
const test = require('node:test');

const cache = require('memory-cache');

const USPS = require('../index');

test('getAccessToken', { concurrency: true }, async (t) => {
const mock = test.mock;

const ACCESS_TOKEN = {
access_token: 'test-access-token',
expires_in: 3600,
token_type: 'Bearer'
};

function jsonResponse(body, status) {
return new Response(JSON.stringify(body), {
headers: { 'Content-Type': 'application/json' },
status: status || 200
});
}

// Replace global fetch with a router for the USPS sandbox endpoints so the
// suite never touches the live API. `overrides` maps a pathname to a function
// returning the Response for that endpoint, letting a test force an error.
function mockUspsFetch(overrides) {
const routes = overrides || {};

mock.method(globalThis, 'fetch', async (url, init) => {
const pathname = new URL(url).pathname;

if (routes[pathname]) {
return routes[pathname](init);
}

if (pathname === '/oauth2/v3/token') {
return jsonResponse(ACCESS_TOKEN);
}

if (pathname === '/tracking/v3r2/tracking') {
const body = JSON.parse(init.body);

if (!body[0] || !body[0].trackingNumber || body[0].trackingNumber === 'null') {
return jsonResponse({ error: 'A valid tracking number is required.' }, 400);
}

return jsonResponse(body.map(item => ({ trackingEvents: [], trackingNumber: item.trackingNumber })));
}

return new Response('Not found', { status: 404 });
});
}

test('getAccessToken', async (t) => {
t.afterEach(() => {
mock.restoreAll();
cache.clear();
});

t.test('should return an error for invalid environment_url', async () => {
// No mock here: this exercises the real fetch URL parsing.
const usps = new USPS({
environment_url: 'invalid'
});
Expand All @@ -13,8 +67,13 @@ test('getAccessToken', { concurrency: true }, async (t) => {
});

t.test('should return an error for non 200 status code', async () => {
mockUspsFetch({
'/oauth2/v3/token': () => new Response('Server error', { status: 500, statusText: 'Internal Server Error' })
});

const usps = new USPS({
environment_url: 'https://httpbin.org/status/500#'
client_id: 'test-client-id',
client_secret: 'test-client-secret'
});

await assert.rejects(usps.getAccessToken(), (err) => {
Expand All @@ -25,9 +84,11 @@ test('getAccessToken', { concurrency: true }, async (t) => {
});

t.test('should return a valid access token', async () => {
mockUspsFetch();

const usps = new USPS({
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
client_id: 'test-client-id',
client_secret: 'test-client-secret'
});

const accessToken = await usps.getAccessToken();
Expand All @@ -39,23 +100,34 @@ test('getAccessToken', { concurrency: true }, async (t) => {
});

t.test('should return the same access token on subsequent calls', async () => {
mockUspsFetch();

const usps = new USPS({
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
client_id: 'test-client-id',
client_secret: 'test-client-secret'
});

const accessToken1 = await usps.getAccessToken();
const accessToken2 = await usps.getAccessToken();

assert.deepStrictEqual(accessToken2, accessToken1);
// The second call should be served from cache, not a second fetch.
assert.strictEqual(globalThis.fetch.mock.calls.length, 1);
});
});

test('getTracking', { concurrency: true }, async (t) => {
test('getTracking', async (t) => {
t.afterEach(() => {
mock.restoreAll();
cache.clear();
});

t.test('should return tracking data for tracking number', async () => {
mockUspsFetch();

const usps = new USPS({
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
client_id: 'test-client-id',
client_secret: 'test-client-secret'
});

const tracking = await usps.getTracking('9434650899562092878282');
Expand All @@ -66,9 +138,11 @@ test('getTracking', { concurrency: true }, async (t) => {
});

t.test('should support destinationZIPCode option', async () => {
mockUspsFetch();

const usps = new USPS({
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
client_id: 'test-client-id',
client_secret: 'test-client-secret'
});

const tracking = await usps.getTracking('9434650899562092878282', {
Expand All @@ -79,9 +153,11 @@ test('getTracking', { concurrency: true }, async (t) => {
});

t.test('should support mailingDate option', async () => {
mockUspsFetch();

const usps = new USPS({
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
client_id: 'test-client-id',
client_secret: 'test-client-secret'
});

const tracking = await usps.getTracking('9434650899562092878282', {
Expand All @@ -92,9 +168,11 @@ test('getTracking', { concurrency: true }, async (t) => {
});

t.test('should handle error for blank tracking number', async () => {
mockUspsFetch();

const usps = new USPS({
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET
client_id: 'test-client-id',
client_secret: 'test-client-secret'
});

await assert.rejects(usps.getTracking(null));
Expand Down