Add paged orders API and infinite scroll, short order numbers, and align the guest invite card with the logged-in hero shell. Co-authored-by: Cursor <cursoragent@cursor.com>
110 lines
4.1 KiB
TypeScript
110 lines
4.1 KiB
TypeScript
import { CustomersService } from './customers.service';
|
|
|
|
/**
|
|
* `GET customer/me/orders` must carry each item's product and variant.
|
|
*
|
|
* The apps used to label order rows by downloading the ENTIRE catalogue and
|
|
* joining it in the browser (composables/useOrderItemProduct.ts). With the real
|
|
* catalogue at ~16k products that is the wrong trade for a few row labels, and
|
|
* it was also broken for the case order history exists for: a product that has
|
|
* since left the storefront is absent from the catalogue, so the row rendered
|
|
* blank.
|
|
*
|
|
* Both apps now read these fields straight off the item, so the include is part
|
|
* of the contract — hence a test, which this method did not have.
|
|
*/
|
|
describe('CustomersService.getOrders — items carry their product', () => {
|
|
function setup() {
|
|
const findMany = jest.fn().mockResolvedValue([]);
|
|
const count = jest.fn().mockResolvedValue(0);
|
|
const prisma = { order: { findMany, count } };
|
|
// getOrders touches neither the accounting sync nor anything on it.
|
|
const service = new CustomersService(prisma as never, {} as never);
|
|
return { service, findMany, count };
|
|
}
|
|
|
|
it('includes the variant, its product and the first image', async () => {
|
|
const { service, findMany } = setup();
|
|
await service.getOrders('c1');
|
|
|
|
const include = findMany.mock.calls[0][0].include;
|
|
const variant = include.items.include.productVariant;
|
|
|
|
expect(variant.select.title).toBe(true);
|
|
expect(variant.select.product.select.title).toBe(true);
|
|
// images[0] is the product's main photo, so it must be ordered and limited
|
|
// to one — the apps read `images[0].url` and nothing else.
|
|
expect(variant.select.product.select.images).toMatchObject({
|
|
orderBy: { sortOrder: 'asc' },
|
|
take: 1,
|
|
});
|
|
});
|
|
|
|
it('includes status events oldest-first for the detail timeline', async () => {
|
|
const { service, findMany } = setup();
|
|
await service.getOrders('c1');
|
|
|
|
expect(findMany.mock.calls[0][0].include.statusEvents).toMatchObject({
|
|
orderBy: { createdAt: 'asc' },
|
|
select: {
|
|
id: true,
|
|
fromStatus: true,
|
|
toStatus: true,
|
|
note: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('is scoped to the caller and newest first', async () => {
|
|
// Guards the two properties that were already here: one customer must never
|
|
// receive another's orders, and the list is reverse-chronological.
|
|
const { service, findMany } = setup();
|
|
await service.getOrders('c1');
|
|
expect(findMany.mock.calls[0][0]).toMatchObject({
|
|
where: { customerId: 'c1' },
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
});
|
|
|
|
it('does not filter the product by storefront visibility', async () => {
|
|
// Deliberate: an order for a product that has since been deactivated, or
|
|
// had its photo removed, must still show what was bought. The storefront
|
|
// rules apply to the catalogue, not to history.
|
|
const { service, findMany } = setup();
|
|
await service.getOrders('c1');
|
|
const product = findMany.mock.calls[0][0].include.items.include.productVariant.select.product;
|
|
expect(product.where).toBeUndefined();
|
|
});
|
|
|
|
it('pages with skip/take and returns count', async () => {
|
|
const { service, findMany, count } = setup();
|
|
findMany.mockResolvedValue([{ id: 'o1' }]);
|
|
count.mockResolvedValue(41);
|
|
|
|
await expect(service.getOrders('c1', { page: 2, limit: 20 })).resolves.toEqual({
|
|
result: [{ id: 'o1' }],
|
|
count: 41,
|
|
page: 2,
|
|
limit: 20,
|
|
});
|
|
expect(findMany.mock.calls[0][0]).toMatchObject({ skip: 20, take: 20 });
|
|
});
|
|
|
|
it('filters by status and orderNumber on the server', async () => {
|
|
const { service, findMany } = setup();
|
|
await service.getOrders('c1', { status: 'PLACED', q: '12' });
|
|
expect(findMany.mock.calls[0][0].where).toEqual({
|
|
customerId: 'c1',
|
|
status: 'PLACED',
|
|
orderNumber: { contains: '12', mode: 'insensitive' },
|
|
});
|
|
});
|
|
|
|
it('ignores an unknown status instead of throwing', async () => {
|
|
const { service, findMany } = setup();
|
|
await service.getOrders('c1', { status: 'NOT_A_STATUS' });
|
|
expect(findMany.mock.calls[0][0].where).toEqual({ customerId: 'c1' });
|
|
});
|
|
});
|