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); }); }); });