/**
* Tests for track_order tool
* Covers: mock data fallback, real API calls, fulfillment mapping, error handling
*/
import type { Order, FulfillmentInfo } from '../../src/types.js';
// ─── Mocks ───
const mockLoadConfig = vi.fn();
vi.mock('../../src/types.js', () => ({
loadConfig: (...args: unknown[]) => mockLoadConfig(...args),
}));
const mockGetOrder = vi.fn();
vi.mock('../../src/shopify/admin.js', () => ({
AdminAPI: vi.fn(() => ({ getOrder: mockGetOrder })),
}));
vi.mock('../../src/shopify/client.js', () => ({
ShopifyClient: vi.fn(),
}));
vi.mock('../../src/utils/logger.js', () => ({
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
}));
import { trackOrder } from '../../src/tools/track-order.js';
// ─── Helpers ───
function configWithToken(accessToken = 'token'): ReturnType<typeof mockLoadConfig> {
return {
shopify: {
storeDomain: 'test.myshopify.com',
accessToken,
storefrontToken: 'sf-token',
},
ap2: {},
gateway: {},
dynamodb: {},
};
}
function makeOrder(overrides: Partial<Order> = {}): Order {
return {
id: 'order-123',
checkout_id: 'checkout-123',
status: 'shipped',
line_items: [
{
id: 'li-1',
product_id: 'prod-1',
variant_id: 'var-1',
title: 'Test Product',
quantity: 1,
unit_amount: 2999,
total_amount: 2999,
type: 'product',
},
],
totals: {
subtotal: 2999,
tax: 240,
shipping: 500,
discount: 0,
fee: 0,
total: 3739,
currency: 'USD',
},
created_at: '2025-01-01T00:00:00Z',
updated_at: '2025-01-15T12:00:00Z',
...overrides,
};
}
function makeFulfillment(overrides: Partial<FulfillmentInfo> = {}): FulfillmentInfo {
return {
status: 'fulfilled',
tracking_number: '1Z999AA1012345',
tracking_url: 'https://ups.com/track?num=1Z999AA1012345',
carrier: 'UPS',
estimated_delivery: '2025-01-20T00:00:00Z',
...overrides,
};
}
// ─── Test Suites ───
describe('trackOrder', () => {
beforeEach(() => {
vi.clearAllMocks();
});
// 1. Returns mock data when no accessToken configured
it('returns mock data when no accessToken is configured', async () => {
mockLoadConfig.mockReturnValue(configWithToken(''));
const result = await trackOrder({ order_id: 'order-mock' });
expect(result.found).toBe(true);
expect(result.order).toBeDefined();
expect(result.order!.id).toBe('order-mock');
expect(result.tracking).toBeDefined();
expect(result.tracking!.status).toBe('in_transit');
expect(result.estimated_delivery).toBeDefined();
// AdminAPI should NOT be called
expect(mockGetOrder).not.toHaveBeenCalled();
});
// 2. Returns order with fulfillment tracking info
it('returns order with fulfillment tracking info from real API', async () => {
mockLoadConfig.mockReturnValue(configWithToken('real-token'));
const fulfillment = makeFulfillment();
const order = makeOrder({ fulfillment });
mockGetOrder.mockResolvedValue(order);
const result = await trackOrder({ order_id: 'order-123' });
expect(result.found).toBe(true);
expect(result.order).toBeDefined();
expect(result.order!.id).toBe('order-123');
expect(result.tracking).toEqual({
status: 'fulfilled',
tracking_number: '1Z999AA1012345',
tracking_url: 'https://ups.com/track?num=1Z999AA1012345',
carrier: 'UPS',
last_update: '2025-01-15T12:00:00Z',
});
expect(result.estimated_delivery).toBe('2025-01-20T00:00:00Z');
});
// 3. Returns order without fulfillment (unfulfilled status)
it('returns unfulfilled tracking status when order has no fulfillment', async () => {
mockLoadConfig.mockReturnValue(configWithToken('real-token'));
const order = makeOrder({ fulfillment: undefined });
mockGetOrder.mockResolvedValue(order);
const result = await trackOrder({ order_id: 'order-unfulfilled' });
expect(result.found).toBe(true);
expect(result.tracking).toEqual({ status: 'unfulfilled' });
expect(result.tracking!.tracking_number).toBeUndefined();
expect(result.tracking!.carrier).toBeUndefined();
expect(result.estimated_delivery).toBeUndefined();
});
// 4. Returns { found: false } when order not found
it('returns found:false when order is not found in Shopify', async () => {
mockLoadConfig.mockReturnValue(configWithToken('real-token'));
mockGetOrder.mockResolvedValue(null);
const result = await trackOrder({ order_id: 'order-missing' });
expect(result.found).toBe(false);
expect(result.order).toBeUndefined();
expect(result.tracking).toBeUndefined();
});
// 5. Returns { found: false } on API error
it('returns found:false when API throws an error', async () => {
mockLoadConfig.mockReturnValue(configWithToken('real-token'));
mockGetOrder.mockRejectedValue(new Error('Shopify API error'));
const result = await trackOrder({ order_id: 'order-error' });
expect(result.found).toBe(false);
expect(result.order).toBeUndefined();
});
// 6. Mock result has correct structure
it('mock result has correct structure with order, tracking, and estimated_delivery', async () => {
mockLoadConfig.mockReturnValue(configWithToken(''));
const result = await trackOrder({ order_id: 'order-structure' });
// Order structure
expect(result.order).toBeDefined();
expect(result.order!.checkout_id).toContain('mock-checkout-');
expect(result.order!.status).toBe('shipped');
expect(result.order!.line_items).toHaveLength(1);
expect(result.order!.line_items[0]!.type).toBe('product');
expect(result.order!.totals).toBeDefined();
expect(result.order!.totals.currency).toBe('USD');
expect(result.order!.totals.total).toBe(3799);
expect(result.order!.fulfillment).toBeDefined();
expect(result.order!.fulfillment!.carrier).toBe('UPS');
// Tracking structure
expect(result.tracking!.tracking_number).toBe('1Z999AA10123456784');
expect(result.tracking!.tracking_url).toContain('ups.com');
expect(result.tracking!.carrier).toBe('UPS');
expect(result.tracking!.last_update).toBeDefined();
// Estimated delivery
expect(result.estimated_delivery).toBeDefined();
const deliveryDate = new Date(result.estimated_delivery!);
expect(deliveryDate.getTime()).toBeGreaterThan(Date.now());
});
// 7. buildTrackingInfo maps fulfillment fields correctly
it('maps fulfillment fields correctly into tracking info', async () => {
mockLoadConfig.mockReturnValue(configWithToken('real-token'));
const fulfillment = makeFulfillment({
status: 'partial',
tracking_number: 'TRACK-XYZ',
tracking_url: 'https://carrier.com/TRACK-XYZ',
carrier: 'FedEx',
estimated_delivery: '2025-02-01T00:00:00Z',
});
const order = makeOrder({
fulfillment,
updated_at: '2025-01-20T08:30:00Z',
});
mockGetOrder.mockResolvedValue(order);
const result = await trackOrder({ order_id: 'order-fedex' });
expect(result.found).toBe(true);
expect(result.tracking).toEqual({
status: 'partial',
tracking_number: 'TRACK-XYZ',
tracking_url: 'https://carrier.com/TRACK-XYZ',
carrier: 'FedEx',
last_update: '2025-01-20T08:30:00Z',
});
expect(result.estimated_delivery).toBe('2025-02-01T00:00:00Z');
});
// Edge: non-Error exception in catch block
it('handles non-Error exceptions gracefully', async () => {
mockLoadConfig.mockReturnValue(configWithToken('real-token'));
mockGetOrder.mockRejectedValue('string error');
const result = await trackOrder({ order_id: 'order-string-err' });
expect(result.found).toBe(false);
});
// Edge: loadConfig itself throws
it('returns found:false when loadConfig throws', async () => {
mockLoadConfig.mockImplementation(() => {
throw new Error('Config missing');
});
const result = await trackOrder({ order_id: 'order-no-config' });
expect(result.found).toBe(false);
});
// Edge: fulfillment with no tracking details
it('returns fulfillment status without tracking details when not available', async () => {
mockLoadConfig.mockReturnValue(configWithToken('real-token'));
const fulfillment = makeFulfillment({
status: 'fulfilled',
tracking_number: undefined,
tracking_url: undefined,
carrier: undefined,
estimated_delivery: undefined,
});
const order = makeOrder({ fulfillment });
mockGetOrder.mockResolvedValue(order);
const result = await trackOrder({ order_id: 'order-no-tracking' });
expect(result.found).toBe(true);
expect(result.tracking!.status).toBe('fulfilled');
expect(result.tracking!.tracking_number).toBeUndefined();
expect(result.tracking!.carrier).toBeUndefined();
expect(result.estimated_delivery).toBeUndefined();
});
// Edge: mock result order_id is reflected in the mock order
it('mock result uses the provided order_id', async () => {
mockLoadConfig.mockReturnValue(configWithToken(''));
const result = await trackOrder({ order_id: 'custom-order-id-999' });
expect(result.order!.id).toBe('custom-order-id-999');
expect(result.order!.checkout_id).toBe('mock-checkout-custom-order-id-999');
});
});