datewise/app/services/__tests__/orders.server.test.ts
metatroncubeswdev e56182e0e3 chore: initial Datewise app (rebranded from SlotSure prototype)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:06:49 -04:00

107 lines
4.6 KiB
TypeScript

/**
* Order webhook → booking, against real Postgres (TEST_DATABASE_URL).
* Admin API calls (tagging/metafields) are skipped by passing no admin client.
*/
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
const url = process.env.TEST_DATABASE_URL;
if (url) process.env.DATABASE_URL = url;
const d = url ? describe : describe.skip;
d("order webhooks", async () => {
const { default: prisma } = await import("../../db.server");
const { handleOrderCreate, handleOrderCancelled } = await import("../orders.server");
const { ReservationService } = await import("../reservations.server");
const { withDefaults } = await import("../settings");
const svc = new ReservationService(prisma);
const SHOP = "orders-test.myshopify.com";
let shopId = "";
let locId = "";
const sel = (o: Record<string, unknown> = {}) => ({ t: "tok_orders_test_0001", m: "delivery", l: locId, d: "2030-06-04", s: "10:00-13:00", f: 799, v: 1, ...o });
const order = (id: number, attr: object | null, feePrice = "7.99") => ({
id,
admin_graphql_api_id: `gid://shopify/Order/${id}`,
name: `#${id}`,
email: "priya@example.com",
phone: "+15195550142",
note_attributes: attr ? [{ name: "_datewise", value: JSON.stringify(attr) }] : [],
customer: { first_name: "Priya", last_name: "R" },
shipping_address: { address1: "1 King St", city: "Kitchener", zip: "N2H 1A1", latitude: 43.45, longitude: -80.49 },
line_items: [
{ title: "Marigold", quantity: 2, price: "19.99", product_id: 101, variant_id: 1001 },
...(feePrice ? [{ title: "Delivery & handling", quantity: 1, price: feePrice, product_id: 900, variant_id: 9000 }] : []),
],
});
beforeAll(async () => {
await prisma.shop.deleteMany({ where: { domain: SHOP } });
});
afterAll(async () => {
await prisma.shop.deleteMany({ where: { domain: SHOP } });
});
beforeEach(async () => {
await prisma.shop.deleteMany({ where: { domain: SHOP } });
const shop = await prisma.shop.create({
data: {
domain: SHOP,
plan: "Growth",
feeProductId: "gid://shopify/Product/900",
settings: withDefaults({}) as object,
locations: {
create: [{
name: "Kitchener",
address: "x",
timezone: "America/Toronto",
methods: { delivery: { enabled: true, weeklyHours: { 2: [{ start: "10:00", end: "19:00" }] }, slotMode: "generated", slotMinutes: 180, prepMinutes: 0, prepMode: "elapsed", horizonDays: 3650, slotCapacity: 1 } },
}],
},
},
include: { locations: true },
});
shopId = shop.id;
locId = shop.locations[0].id;
});
const hold = (token = "tok_orders_test_0001", fee = 799) =>
svc.hold(shopId, token, { locationId: locId, method: "delivery", date: "2030-06-04", slot: "10:00-13:00" }, { dayCap: null, slotCap: 1 }, new Date(), 10, fee);
it("confirms the held place, stores contact + items, plans reminders", async () => {
await hold();
const r = await handleOrderCreate(SHOP, order(1, sel()));
expect("ok" in r && r.ok).toBe(true);
const b = await prisma.booking.findFirstOrThrow({ where: { shopId }, include: { reminderJobs: true } });
expect(b).toMatchObject({ status: "confirmed", customerName: "Priya R", fee: 799, slotStart: "10:00", reviewReason: null });
expect(b.startUtc?.toISOString()).toBe("2030-06-04T14:00:00.000Z");
expect(b.reminderJobs.length).toBeGreaterThan(0);
});
it("is idempotent for the same order", async () => {
await hold();
await handleOrderCreate(SHOP, order(2, sel()));
await handleOrderCreate(SHOP, order(2, sel()));
expect(await prisma.booking.count({ where: { shopId } })).toBe(1);
});
it("flags a fee that doesn't match the quote, but keeps the place", async () => {
await hold();
await handleOrderCreate(SHOP, order(3, sel(), "0.01"));
const b = await prisma.booking.findFirstOrThrow({ where: { shopId } });
expect(b.status).toBe("confirmed");
expect(b.reviewReason).toMatch(/Fee paid 0.01 differs from quote 7.99/);
});
it("orders without a time go to review when scheduling is required", async () => {
await handleOrderCreate(SHOP, order(4, null, ""));
const b = await prisma.booking.findFirstOrThrow({ where: { shopId } });
expect(b.status).toBe("needs_review");
});
it("cancellation frees the place for the next customer", async () => {
await hold();
await handleOrderCreate(SHOP, order(5, sel()));
expect((await hold("tok_orders_test_0002")).ok).toBe(false);
await handleOrderCancelled(SHOP, { admin_graphql_api_id: "gid://shopify/Order/5" });
expect((await hold("tok_orders_test_0002")).ok).toBe(true);
});
});