Files
doormilxpress_astryx/tests/api/deliveries.test.js
2026-08-27 16:49:09 +05:30

626 lines
27 KiB
JavaScript

jest.mock('@/api/doormile/endpoints', () => ({
__esModule: true,
getBookings: jest.fn(),
getBooking: jest.fn(),
getConsignments: jest.fn(),
getAdminCustomers: jest.fn(),
getAdminTenants: jest.fn(),
getMilers: jest.fn(),
getMiler: jest.fn(),
getMilerSummary: jest.fn(),
getMilerLogs: jest.fn(),
notifyMiler: jest.fn(),
getTenantLocations: jest.fn(),
getHubs: jest.fn(),
assignMilerToBooking: jest.fn(),
cancelBooking: jest.fn(),
updateConsignmentStatus: jest.fn(),
getAdminPricing: jest.fn(),
getReports: jest.fn(),
getLocationsSummary: jest.fn(),
getProfile: jest.fn()
}));
jest.mock('@/api/doormile/notify', () => ({
__esModule: true,
OpenToast: jest.fn(),
messageOf: jest.fn((err, fallback) => err?.message || fallback)
}));
import * as endpoints from '@/api/doormile/endpoints';
import { OpenToast } from '@/api/doormile/notify';
import {
fetchDeliveries,
fetchCountAPI,
cancelDeliveryAPI,
changeRiderAPI,
updateDeliveryAPI,
getorderdetails
} from '@/api/doormile/queries';
/**
* A booking as GET /admin/bookings returns it. Defaults make the row
* "dispatched" (it carries an assigned rider), because fetchDeliveries only
* surfaces dispatched rows.
*/
const booking = (over = {}) => ({
bookingid: 4021,
bookingno: 'DM-4021',
status: 'Miler_Assigned',
assignedmileruserid: 91,
tenantid: 4,
createdat: '2026-08-27T10:00:00',
updatedat: '2026-08-27T10:05:00',
pickupaddress: '12 MG Road, Bengaluru',
deliveryaddress: '44 Indiranagar, Bengaluru',
...over
});
/** The query key every caller of fetchDeliveries builds. */
const key = ({ status = 'all', startdate, enddate, rowsPerPage = 100 } = {}) => [
'deliveries',
1,
null,
status,
startdate,
enddate,
rowsPerPage
];
const withNoJoins = () => {
endpoints.getAdminCustomers.mockResolvedValue([]);
endpoints.getMilers.mockResolvedValue([]);
endpoints.getAdminTenants.mockResolvedValue([]);
endpoints.getConsignments.mockResolvedValue([]);
};
describe('deliveries data layer', () => {
beforeEach(withNoJoins);
describe('fetchDeliveries — dispatched filtering', () => {
it('should surface a booking that already has a rider assigned', async () => {
endpoints.getBookings.mockResolvedValue([booking()]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows).toHaveLength(1);
expect(rows[0]).toMatchObject({ bookingid: 4021, deliveryid: 4021, orderheaderid: 4021 });
});
it.each(['pending_pickup', 'created', 'new', 'booked', 'order_placed', 'unassigned'])(
'should exclude an undispatched %s booking with no rider and no consignment',
async (status) => {
endpoints.getBookings.mockResolvedValue([
booking({ status, assignedmileruserid: null, consignmentid: null })
]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows).toEqual([]);
}
);
it('should surface an undispatched-looking booking that nonetheless has a consignment', async () => {
endpoints.getBookings.mockResolvedValue([
booking({ status: 'pending_pickup', assignedmileruserid: null, consignmentid: 40 })
]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows).toHaveLength(1);
expect(rows[0].consignmentid).toBe(40);
});
it('should surface a booking whose status has moved past the pre-dispatch set', async () => {
endpoints.getBookings.mockResolvedValue([
booking({ status: 'Out_for_Delivery', assignedmileruserid: null, consignmentid: null })
]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows).toHaveLength(1);
});
});
describe('fetchDeliveries — row mapping', () => {
it('should resolve the rider name from the joined miler list', async () => {
endpoints.getBookings.mockResolvedValue([booking()]);
endpoints.getMilers.mockResolvedValue([
{ userid: 91, milerprofileid: 11, firstname: 'Anita', lastname: 'Rao', phone: '9876543210' }
]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0]).toMatchObject({
ridername: 'Anita Rao',
userid: 91,
milerprofileid: 11,
ridercontact: '9876543210'
});
});
it('should fall back to a numbered rider label when the miler join fails', async () => {
// A failed join must degrade a display field, not blank the page.
endpoints.getBookings.mockResolvedValue([booking()]);
endpoints.getMilers.mockRejectedValue(new Error('milers 500'));
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows).toHaveLength(1);
expect(rows[0].ridername).toBe('Rider #91');
});
it('should resolve the delivery customer from the joined customer list', async () => {
endpoints.getBookings.mockResolvedValue([booking({ appcustomerid: 7 })]);
endpoints.getAdminCustomers.mockResolvedValue([
{ appcustomerid: 7, firstname: 'Vikram', phone: '9000000000' }
]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0]).toMatchObject({ deliverycustomer: 'Vikram', deliverycontactno: '9000000000', deliverytype: 'B' });
});
it('should mark a row with no matched customer as a C-type delivery', async () => {
endpoints.getBookings.mockResolvedValue([booking()]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0].deliverytype).toBe('C');
});
it('should bucket on the booking creation time, exposed as orderdate', async () => {
// Batch bucketing and date filtering both read orderdate; if this stops
// being createdat, rows are counted for a day but belong to no batch.
endpoints.getBookings.mockResolvedValue([booking({ createdat: '2026-08-27T07:00:00' })]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0].orderdate).toBe('2026-08-27T07:00:00');
});
it('should approximate distance as a straight line between the two coordinates', async () => {
endpoints.getBookings.mockResolvedValue([
booking({
pickuplatitude: 12.9716,
pickuplongitude: 77.5946,
deliverylatitude: 12.9352,
deliverylongitude: 77.6245
})
]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0].kms).toBeGreaterThan(4);
expect(rows[0].kms).toBeLessThan(7);
});
it('should leave distance undefined — not zero — when a coordinate is missing', async () => {
// A fabricated 0 km reads as a measured distance and feeds the rate card.
endpoints.getBookings.mockResolvedValue([booking({ pickuplatitude: null })]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0].kms).toBeUndefined();
});
it('should leave route-plan fields undefined rather than fabricating them', async () => {
endpoints.getBookings.mockResolvedValue([booking()]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0].transitminutes).toBeUndefined();
expect(rows[0].cumulativekms).toBeUndefined();
expect(rows[0].step).toBeUndefined();
});
it('should count parcels as the row quantity, defaulting to zero', async () => {
endpoints.getBookings.mockResolvedValue([booking({ parcels: [{}, {}] }), booking({ bookingid: 4022 })]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0].quantity).toBe(2);
expect(rows[1].quantity).toBe(0);
});
it('should carry the service charge through to every amount field the table reads', async () => {
endpoints.getBookings.mockResolvedValue([
booking({ serviceoptions: [{ estimatedprice: 240, estimateddeliveryat: '2026-08-27T12:00:00' }] })
]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0]).toMatchObject({ deliverycharges: 240, deliveryamt: 240, deliveryamount: 240 });
expect(rows[0].expecteddeliverytime).toBe('2026-08-27T12:00:00');
});
it('should let the consignment status drive the row status', async () => {
endpoints.getBookings.mockResolvedValue([
booking({ status: 'Converted_To_Consignment', consignmentid: 40 })
]);
endpoints.getConsignments.mockResolvedValue([{ consignmentid: 40, status: 'Out_for_Delivery' }]);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0].orderstatus).toBe('active');
expect(rows[0].statusfromconsignment).toBe(true);
});
it('should fall back to the booking status when the consignment join fails', async () => {
endpoints.getBookings.mockResolvedValue([
booking({ status: 'Converted_To_Consignment', consignmentid: 40 })
]);
endpoints.getConsignments.mockRejectedValue(new Error('consignments 500'));
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows[0].orderstatus).toBe('picked');
});
});
describe('fetchDeliveries — date range', () => {
const threeDays = [
booking({ bookingid: 1, createdat: '2026-08-25T10:00:00' }),
booking({ bookingid: 2, createdat: '2026-08-26T10:00:00' }),
booking({ bookingid: 3, createdat: '2026-08-27T10:00:00' })
];
it('should return every row when no range is given', async () => {
endpoints.getBookings.mockResolvedValue(threeDays);
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows).toHaveLength(3);
});
it('should apply the requested range instead of ignoring it', async () => {
// The regression: startdate/enddate were accepted and then dropped, so
// today, yesterday and last week all produced a byte-identical row set.
endpoints.getBookings.mockResolvedValue(threeDays);
const { rows } = await fetchDeliveries({
pageParam: 1,
queryKey: key({ startdate: '2026-08-26', enddate: '2026-08-26' })
});
expect(rows.map((r) => r.bookingid)).toEqual([2]);
});
it('should include both range boundaries', async () => {
endpoints.getBookings.mockResolvedValue(threeDays);
const { rows } = await fetchDeliveries({
pageParam: 1,
queryKey: key({ startdate: '2026-08-25', enddate: '2026-08-27' })
});
expect(rows).toHaveLength(3);
});
it('should treat a missing bound as unbounded on that side', async () => {
endpoints.getBookings.mockResolvedValue(threeDays);
const from = await fetchDeliveries({ pageParam: 1, queryKey: key({ startdate: '2026-08-26' }) });
expect(from.rows.map((r) => r.bookingid)).toEqual([2, 3]);
const until = await fetchDeliveries({ pageParam: 1, queryKey: key({ enddate: '2026-08-26' }) });
expect(until.rows.map((r) => r.bookingid)).toEqual([1, 2]);
});
it('should keep a late-evening row on its own day despite a false trailing Z', async () => {
// Unstripped, 23:45Z shifts +5:30 into the next day and drops out of the
// selected date entirely.
endpoints.getBookings.mockResolvedValue([booking({ createdat: '2026-08-27T23:45:00Z' })]);
const { rows } = await fetchDeliveries({
pageParam: 1,
queryKey: key({ startdate: '2026-08-27', enddate: '2026-08-27' })
});
expect(rows).toHaveLength(1);
});
it('should drop a row with an unparseable creation date when a range is set', async () => {
endpoints.getBookings.mockResolvedValue([booking({ createdat: 'garbage' })]);
const { rows } = await fetchDeliveries({
pageParam: 1,
queryKey: key({ startdate: '2026-08-27', enddate: '2026-08-27' })
});
expect(rows).toEqual([]);
});
it('should return an empty page for a range with no rows', async () => {
endpoints.getBookings.mockResolvedValue(threeDays);
const { rows } = await fetchDeliveries({
pageParam: 1,
queryKey: key({ startdate: '2026-01-01', enddate: '2026-01-02' })
});
expect(rows).toEqual([]);
});
});
describe('fetchDeliveries — pagination and failure', () => {
it('should advertise a next page when the server returned a full page', async () => {
endpoints.getBookings.mockResolvedValue(Array.from({ length: 25 }, (_, i) => booking({ bookingid: i + 1 })));
const result = await fetchDeliveries({ pageParam: 1, queryKey: key({ rowsPerPage: 25 }) });
expect(result.nextPage).toBe(2);
});
it('should stop paginating on a partial page', async () => {
endpoints.getBookings.mockResolvedValue(Array.from({ length: 9 }, (_, i) => booking({ bookingid: i + 1 })));
const result = await fetchDeliveries({ pageParam: 1, queryKey: key({ rowsPerPage: 25 }) });
expect(result.nextPage).toBeUndefined();
});
it('should request the page the caller asked for', async () => {
endpoints.getBookings.mockResolvedValue([]);
await fetchDeliveries({ pageParam: 3, queryKey: key({ rowsPerPage: 50 }) });
expect(endpoints.getBookings).toHaveBeenCalledWith(3, 50);
});
it('should default to the first page when pageParam is omitted', async () => {
endpoints.getBookings.mockResolvedValue([]);
await fetchDeliveries({ queryKey: key({ rowsPerPage: 50 }) });
expect(endpoints.getBookings).toHaveBeenCalledWith(1, 50);
});
it('should toast and return an empty page when the bookings call fails', async () => {
// The one join it cannot recover from — previously it rejected uncaught,
// with no toast anywhere across the four pages that call this.
endpoints.getBookings.mockRejectedValue({ message: 'bookings 503' });
const result = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(result).toEqual({ rows: [], nextPage: undefined });
expect(OpenToast).toHaveBeenCalledWith('bookings 503', 'error', 2000);
});
it('should still render rows when every optional join fails', async () => {
endpoints.getBookings.mockResolvedValue([booking()]);
endpoints.getAdminCustomers.mockRejectedValue(new Error('x'));
endpoints.getMilers.mockRejectedValue(new Error('x'));
endpoints.getAdminTenants.mockRejectedValue(new Error('x'));
endpoints.getConsignments.mockRejectedValue(new Error('x'));
const { rows } = await fetchDeliveries({ pageParam: 1, queryKey: key() });
expect(rows).toHaveLength(1);
});
it('should return an empty page for an account with no bookings', async () => {
endpoints.getBookings.mockResolvedValue([]);
await expect(fetchDeliveries({ pageParam: 1, queryKey: key() })).resolves.toEqual({
rows: [],
nextPage: undefined
});
});
});
describe('fetchCountAPI', () => {
it('should count dispatched rows into the page chip buckets', async () => {
endpoints.getBookings.mockResolvedValue([
booking({ bookingid: 1, status: 'Miler_Assigned' }),
booking({ bookingid: 2, status: 'Pickup_Scheduled' }),
booking({ bookingid: 3, status: 'Converted_To_Consignment' }),
booking({ bookingid: 4, status: 'Out_for_Delivery' }),
booking({ bookingid: 5, status: 'Delivered' }),
booking({ bookingid: 6, status: 'Cancelled' })
]);
await expect(fetchCountAPI()).resolves.toEqual({
total: 6,
uncoveredLength: 1,
assignedLength: 1,
arrivedLength: 0,
pickedLength: 1,
activeLength: 1,
coveredLength: 1,
cancelLength: 1,
skippedLength: 0
});
});
it('should count only dispatched bookings, matching what the table shows', async () => {
endpoints.getBookings.mockResolvedValue([
booking({ bookingid: 1, status: 'pending_pickup', assignedmileruserid: null }),
booking({ bookingid: 2, status: 'Delivered' })
]);
const counts = await fetchCountAPI();
expect(counts.total).toBe(1);
expect(counts.coveredLength).toBe(1);
});
it('should report zeroes — not undefined — for an empty account', async () => {
endpoints.getBookings.mockResolvedValue([]);
const counts = await fetchCountAPI();
Object.values(counts).forEach((value) => expect(value).toBe(0));
});
it('should report zeroes rather than throwing when the source call fails', async () => {
endpoints.getBookings.mockRejectedValue(new Error('boom'));
const counts = await fetchCountAPI();
expect(counts.total).toBe(0);
});
});
describe('cancelDeliveryAPI', () => {
it('should cancel through the booking-level cancel action', async () => {
endpoints.cancelBooking.mockResolvedValue({ success: true });
await cancelDeliveryAPI({ orderheaderid: 4021 }, 'customer unreachable');
expect(endpoints.cancelBooking).toHaveBeenCalledWith(4021, { reason: 'customer unreachable' });
});
it('should fall back to deliveryid when the row carries no orderheaderid', async () => {
endpoints.cancelBooking.mockResolvedValue({ success: true });
await cancelDeliveryAPI({ deliveryid: 4022 }, 'duplicate');
expect(endpoints.cancelBooking).toHaveBeenCalledWith(4022, { reason: 'duplicate' });
});
it('should send an undefined reason rather than inventing one', async () => {
endpoints.cancelBooking.mockResolvedValue({ success: true });
await cancelDeliveryAPI({ orderheaderid: 4021 });
expect(endpoints.cancelBooking).toHaveBeenCalledWith(4021, { reason: undefined });
});
it('should surface a rejected cancel to the caller', async () => {
endpoints.cancelBooking.mockRejectedValue({ message: 'already cancelled', httpStatus: 409 });
await expect(cancelDeliveryAPI({ orderheaderid: 4021 }, 'x')).rejects.toMatchObject({ httpStatus: 409 });
});
describe('a row that has already converted to a consignment', () => {
const converted = { bookingid: 41, deliveryid: 41, orderheaderid: 41, consignmentid: 61 };
it('should cancel on the consignment, where the lifecycle now lives', async () => {
// A booking freezes the moment the parcel is picked up; the booking-level
// cancel route is refused for it.
endpoints.updateConsignmentStatus.mockResolvedValue({ success: true });
await cancelDeliveryAPI(converted, 'customer unreachable');
expect(endpoints.updateConsignmentStatus).toHaveBeenCalledWith(61, { status: 'Cancelled' });
});
it('should NOT also call the booking cancel route', async () => {
endpoints.updateConsignmentStatus.mockResolvedValue({ success: true });
await cancelDeliveryAPI(converted, 'x');
expect(endpoints.cancelBooking).not.toHaveBeenCalled();
});
it('should surface the consignment failure instead of retrying on the booking', async () => {
// The reported bug: PUT /admin/consignments/61/status returned 500, the
// code silently fell back to POST /admin/bookings/41/cancel, and the
// operator was shown that route's 400 — an error about the wrong
// endpoint entirely, which is why the real fault was invisible.
endpoints.updateConsignmentStatus.mockRejectedValue({
message: 'failed to update consignment status',
httpStatus: 500
});
endpoints.cancelBooking.mockRejectedValue({ message: 'booking cannot be cancelled', httpStatus: 400 });
await expect(cancelDeliveryAPI(converted, 'x')).rejects.toMatchObject({
message: 'failed to update consignment status',
httpStatus: 500
});
expect(endpoints.cancelBooking).not.toHaveBeenCalled();
});
it('should surface a refusal envelope from the consignment route unchanged', async () => {
endpoints.updateConsignmentStatus.mockResolvedValue({ success: false, message: 'already delivered' });
await expect(cancelDeliveryAPI(converted, 'x')).resolves.toEqual({
success: false,
message: 'already delivered'
});
expect(endpoints.cancelBooking).not.toHaveBeenCalled();
});
it('should never cancel a booking whose consignment is still live', async () => {
// The integrity hazard behind the removed fallback: a cancelled booking
// sitting against a consignment that is still out for delivery.
endpoints.updateConsignmentStatus.mockRejectedValue({ httpStatus: 500, message: 'boom' });
await cancelDeliveryAPI(converted, 'x').catch(() => {});
expect(endpoints.cancelBooking).not.toHaveBeenCalled();
});
});
it('should still use the booking route for a row that has not converted', async () => {
endpoints.cancelBooking.mockResolvedValue({ success: true });
await cancelDeliveryAPI({ bookingid: 41, consignmentid: null }, 'duplicate');
expect(endpoints.cancelBooking).toHaveBeenCalledWith(41, { reason: 'duplicate' });
expect(endpoints.updateConsignmentStatus).not.toHaveBeenCalled();
});
it('should refuse a row carrying neither id rather than calling an endpoint with undefined', async () => {
await expect(cancelDeliveryAPI({}, 'x')).rejects.toThrow(/nothing to cancel/i);
expect(endpoints.cancelBooking).not.toHaveBeenCalled();
expect(endpoints.updateConsignmentStatus).not.toHaveBeenCalled();
});
});
describe('changeRiderAPI', () => {
it("should send the miler's USERID under the mileruserid key", async () => {
// assign-miler is the one admin route keyed on userid rather than
// milerprofileid. The previous guess was wrong on both key and value.
endpoints.assignMilerToBooking.mockResolvedValue({ success: true });
await changeRiderAPI({ userid: 91, milerprofileid: 11 }, { orderheaderid: 4021 });
expect(endpoints.assignMilerToBooking).toHaveBeenCalledWith(4021, { mileruserid: 91 });
});
it('should not send the milerprofileid, which this route does not accept', async () => {
endpoints.assignMilerToBooking.mockResolvedValue({ success: true });
await changeRiderAPI({ userid: 91, milerprofileid: 11 }, { orderheaderid: 4021 });
const [, body] = endpoints.assignMilerToBooking.mock.calls[0];
expect(body).not.toHaveProperty('milerid');
expect(body).not.toHaveProperty('milerprofileid');
});
it('should fall back to deliveryid for the booking id', async () => {
endpoints.assignMilerToBooking.mockResolvedValue({ success: true });
await changeRiderAPI({ userid: 91 }, { deliveryid: 4022 });
expect(endpoints.assignMilerToBooking).toHaveBeenCalledWith(4022, { mileruserid: 91 });
});
it('should surface a rejected assignment to the caller', async () => {
endpoints.assignMilerToBooking.mockRejectedValue({ message: 'miler unavailable', httpStatus: 409 });
await expect(changeRiderAPI({ userid: 91 }, { orderheaderid: 4021 })).rejects.toMatchObject({
httpStatus: 409
});
});
});
describe('updateDeliveryAPI', () => {
beforeEach(() => endpoints.updateConsignmentStatus.mockResolvedValue({ success: true }));
it.each([
['started', 'Out_for_Delivery'],
['active', 'Out_for_Delivery'],
['delivered', 'Delivered'],
['cancelled', 'Cancelled'],
['canceled', 'Cancelled']
])('should translate the dialog choice %s to the consignment enum %s', async (chosen, sent) => {
await updateDeliveryAPI({ consignmentid: 40, orderstatus: chosen });
expect(endpoints.updateConsignmentStatus).toHaveBeenCalledWith(40, { status: sent });
});
it('should be case-insensitive about the dialog choice', async () => {
await updateDeliveryAPI({ consignmentid: 40, orderstatus: 'Delivered' });
expect(endpoints.updateConsignmentStatus).toHaveBeenCalledWith(40, { status: 'Delivered' });
});
it('should target the CONSIGNMENT id, not the booking id', async () => {
// deliveryid is always the bookingid, so `deliveryid ?? consignmentid`
// never fell through — every submit hit the endpoint with the wrong id.
await updateDeliveryAPI({ consignmentid: 40, deliveryid: 4021, orderstatus: 'delivered' });
expect(endpoints.updateConsignmentStatus).toHaveBeenCalledWith(40, expect.anything());
});
it('should send only `status` — the other dialog fields have no field here', async () => {
// Forwarding the whole dialog state produced 400 {"status is required"}.
await updateDeliveryAPI({
consignmentid: 40,
orderstatus: 'delivered',
deliveryamt: 240,
cumulativekms: 12,
userid: 91,
notes: 'left with security'
});
const [, body] = endpoints.updateConsignmentStatus.mock.calls[0];
expect(body).toEqual({ status: 'Delivered' });
});
it('should refuse with a prompt when no status was chosen', async () => {
await expect(updateDeliveryAPI({ consignmentid: 40, orderstatus: '' })).resolves.toEqual({
success: false,
message: 'Choose a status first.'
});
expect(endpoints.updateConsignmentStatus).not.toHaveBeenCalled();
});
it.each([
['picked', 'already a consignment'],
['pending', 'only exists after pickup'],
['accepted', 'only exists after pickup'],
['arrived', 'nothing to set'],
['skipped', 'attempt count']
])('should refuse %s with a specific reason rather than guessing a status', async (chosen, fragment) => {
const result = await updateDeliveryAPI({ consignmentid: 40, orderstatus: chosen });
expect(result.success).toBe(false);
expect(result.message).toContain(fragment);
expect(endpoints.updateConsignmentStatus).not.toHaveBeenCalled();
});
it('should refuse an unrecognised status naming the value that was rejected', async () => {
const result = await updateDeliveryAPI({ consignmentid: 40, orderstatus: 'teleported' });
expect(result.success).toBe(false);
expect(result.message).toContain('teleported');
expect(endpoints.updateConsignmentStatus).not.toHaveBeenCalled();
});
it('should never write a pre-pickup booking state to the consignment endpoint', async () => {
await Promise.all(
['picked', 'pending', 'accepted', 'arrived', 'skipped'].map((orderstatus) =>
updateDeliveryAPI({ consignmentid: 40, orderstatus })
)
);
expect(endpoints.updateConsignmentStatus).not.toHaveBeenCalled();
});
it('should surface a rejected status write to the caller', async () => {
endpoints.updateConsignmentStatus.mockRejectedValue({ message: 'status is required', httpStatus: 400 });
await expect(updateDeliveryAPI({ consignmentid: 40, orderstatus: 'delivered' })).rejects.toMatchObject({
httpStatus: 400
});
});
});
describe('getorderdetails', () => {
it('should read a single booking by id', async () => {
endpoints.getBooking.mockResolvedValue({ bookingid: 4021 });
await expect(getorderdetails(4021)).resolves.toEqual({ bookingid: 4021 });
expect(endpoints.getBooking).toHaveBeenCalledWith(4021);
});
});
});