305 lines
8.4 KiB
JavaScript
305 lines
8.4 KiB
JavaScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const { fetchMock, invoke, rpc } = vi.hoisted(() => ({
|
|
fetchMock: vi.fn(),
|
|
invoke: vi.fn(),
|
|
rpc: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../supabaseClient", () => ({
|
|
hasSupabaseConfig: true,
|
|
supabaseAnonKey: "anon-key",
|
|
supabaseUrl: "https://supa.example.test",
|
|
supabase: {
|
|
rpc,
|
|
functions: {
|
|
invoke,
|
|
},
|
|
},
|
|
}));
|
|
|
|
import {
|
|
buildShowcaseInvitation,
|
|
confirmDeliveryChoice,
|
|
fetchDeliveryInvitation,
|
|
__resetLocalDeliveryInvitationCache,
|
|
reportDeliveryResult,
|
|
requestDeliveryLink,
|
|
transferDeliveryToLogistics,
|
|
} from "./deliveryInvitationApi";
|
|
|
|
describe("deliveryInvitationApi", () => {
|
|
beforeEach(() => {
|
|
fetchMock.mockReset();
|
|
invoke.mockReset();
|
|
rpc.mockReset();
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
__resetLocalDeliveryInvitationCache();
|
|
const storage = new Map();
|
|
vi.stubGlobal("localStorage", {
|
|
getItem: (key) => storage.get(key) ?? null,
|
|
setItem: (key, value) => {
|
|
storage.set(key, String(value));
|
|
},
|
|
removeItem: (key) => {
|
|
storage.delete(key);
|
|
},
|
|
clear: () => {
|
|
storage.clear();
|
|
},
|
|
});
|
|
});
|
|
|
|
it("loads a delivery invitation by token", async () => {
|
|
rpc.mockResolvedValueOnce({
|
|
data: {
|
|
ok: true,
|
|
invitation: {
|
|
orderId: "order-1",
|
|
token: "token-1",
|
|
},
|
|
},
|
|
error: null,
|
|
});
|
|
|
|
await expect(fetchDeliveryInvitation("token-1")).resolves.toEqual({
|
|
orderId: "order-1",
|
|
token: "token-1",
|
|
});
|
|
|
|
expect(rpc).toHaveBeenCalledWith("get_delivery_invitation_by_token", {
|
|
p_token: "token-1",
|
|
});
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
expect(invoke).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("throws a readable error when loading invitation fails", async () => {
|
|
rpc.mockResolvedValueOnce({
|
|
data: null,
|
|
error: new Error("Invitation not found"),
|
|
});
|
|
|
|
await expect(fetchDeliveryInvitation("token-1")).rejects.toThrow("Invitation not found");
|
|
});
|
|
|
|
it("returns a local showcase invitation for the preview token", async () => {
|
|
await expect(fetchDeliveryInvitation("showcase")).resolves.toMatchObject({
|
|
token: "showcase",
|
|
orderNumber: "CD-CLIENT-001",
|
|
customerName: "Мария Волкова",
|
|
state: "awaiting_choice",
|
|
});
|
|
|
|
expect(invoke).toHaveBeenCalledWith("get-delivery-invitation", {
|
|
body: {
|
|
token: "showcase",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("falls back to a local client invitation when the edge function fails", async () => {
|
|
invoke.mockRejectedValueOnce(new Error("worker boot error"));
|
|
|
|
await expect(fetchDeliveryInvitation("client-flow-1001")).resolves.toMatchObject({
|
|
token: "client-flow-1001",
|
|
orderNumber: "CD-240031",
|
|
customerName: "Мария Волкова",
|
|
state: "awaiting_choice",
|
|
orderItems: [
|
|
{ name: "Кухонный гарнитур", quantity: "1 комплект" },
|
|
{ name: "Фурнитура Blum", quantity: "12 шт" },
|
|
{ name: "Монтажный комплект", quantity: "1 набор" },
|
|
],
|
|
availableSlots: expect.arrayContaining([
|
|
expect.stringMatching(/, До обеда$/),
|
|
expect.stringMatching(/, После обеда$/),
|
|
]),
|
|
});
|
|
|
|
expect(invoke).toHaveBeenCalledWith("get-delivery-invitation", {
|
|
body: {
|
|
token: "client-flow-1001",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("builds showcase slots for tomorrow and the following day", () => {
|
|
const invitation = buildShowcaseInvitation("showcase", new Date("2026-04-14T09:00:00Z"));
|
|
|
|
expect(invitation.availableSlots).toEqual([
|
|
"2026-04-15, До обеда",
|
|
"2026-04-15, После обеда",
|
|
"2026-04-16, До обеда",
|
|
"2026-04-16, После обеда",
|
|
]);
|
|
});
|
|
|
|
it("includes readable order items in the showcase invitation", () => {
|
|
const invitation = buildShowcaseInvitation("showcase", new Date("2026-04-14T09:00:00Z"));
|
|
|
|
expect(invitation.orderItems).toEqual([
|
|
{ name: "Кухонный гарнитур", quantity: "1 комплект" },
|
|
{ name: "Фурнитура Blum", quantity: "12 шт" },
|
|
{ name: "Монтажный комплект", quantity: "1 набор" },
|
|
]);
|
|
});
|
|
|
|
it("confirms a delivery choice with the chosen slot", async () => {
|
|
rpc.mockResolvedValueOnce({
|
|
data: {
|
|
ok: true,
|
|
orderId: "order-1",
|
|
},
|
|
error: null,
|
|
});
|
|
|
|
await expect(
|
|
confirmDeliveryChoice({
|
|
token: "token-1",
|
|
deliveryDate: "2026-04-01",
|
|
deliveryTime: "Первая половина дня",
|
|
}),
|
|
).resolves.toEqual({
|
|
ok: true,
|
|
orderId: "order-1",
|
|
});
|
|
|
|
expect(rpc).toHaveBeenCalledWith("confirm_delivery_choice_by_token", {
|
|
p_token: "token-1",
|
|
p_delivery_date: "2026-04-01",
|
|
p_delivery_time: "Первая половина дня",
|
|
});
|
|
expect(invoke).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("updates the local client invitation when confirmation falls back to cache", async () => {
|
|
invoke.mockRejectedValueOnce(new Error("worker boot error"));
|
|
await fetchDeliveryInvitation("client-flow-1001");
|
|
|
|
invoke.mockRejectedValueOnce(new Error("worker boot error"));
|
|
await expect(
|
|
confirmDeliveryChoice({
|
|
token: "client-flow-1001",
|
|
deliveryDate: "2026-04-16",
|
|
deliveryTime: "После обеда",
|
|
}),
|
|
).resolves.toMatchObject({
|
|
ok: true,
|
|
invitation: {
|
|
token: "client-flow-1001",
|
|
deliveryDate: "2026-04-16",
|
|
deliveryTime: "После обеда",
|
|
state: "confirmed",
|
|
},
|
|
});
|
|
|
|
await expect(fetchDeliveryInvitation("client-flow-1001")).resolves.toMatchObject({
|
|
token: "client-flow-1001",
|
|
deliveryDate: "2026-04-16",
|
|
deliveryTime: "После обеда",
|
|
state: "confirmed",
|
|
});
|
|
});
|
|
|
|
it("restores the saved local client invitation after cache reset", async () => {
|
|
invoke.mockRejectedValueOnce(new Error("worker boot error"));
|
|
await fetchDeliveryInvitation("client-flow-1001");
|
|
|
|
invoke.mockRejectedValueOnce(new Error("worker boot error"));
|
|
await confirmDeliveryChoice({
|
|
token: "client-flow-1001",
|
|
deliveryDate: "2026-04-16",
|
|
deliveryTime: "После обеда",
|
|
});
|
|
|
|
__resetLocalDeliveryInvitationCache();
|
|
|
|
await expect(fetchDeliveryInvitation("client-flow-1001")).resolves.toMatchObject({
|
|
token: "client-flow-1001",
|
|
deliveryDate: "2026-04-16",
|
|
deliveryTime: "После обеда",
|
|
state: "confirmed",
|
|
});
|
|
});
|
|
|
|
it("creates a delivery invitation from order data", async () => {
|
|
invoke.mockResolvedValueOnce({
|
|
data: {
|
|
ok: true,
|
|
invitation: {
|
|
orderId: "order-1",
|
|
},
|
|
},
|
|
error: null,
|
|
});
|
|
|
|
await expect(
|
|
requestDeliveryLink({
|
|
orderId: "order-1",
|
|
orderNumber: "CD-240031",
|
|
customerName: "Мария Волкова",
|
|
availableSlots: ["Первая половина дня"],
|
|
}),
|
|
).resolves.toEqual({
|
|
ok: true,
|
|
invitation: {
|
|
orderId: "order-1",
|
|
},
|
|
});
|
|
|
|
expect(invoke).toHaveBeenCalledWith("create-delivery-invitation", {
|
|
body: {
|
|
orderId: "order-1",
|
|
orderNumber: "CD-240031",
|
|
customerName: "Мария Волкова",
|
|
availableSlots: ["Первая половина дня"],
|
|
},
|
|
});
|
|
});
|
|
|
|
it("transfers the order to logistics", async () => {
|
|
invoke.mockResolvedValueOnce({
|
|
data: { ok: true },
|
|
error: null,
|
|
});
|
|
|
|
await expect(
|
|
transferDeliveryToLogistics({
|
|
orderId: "order-1",
|
|
reason: "no_response",
|
|
}),
|
|
).resolves.toEqual({ ok: true });
|
|
|
|
expect(invoke).toHaveBeenCalledWith("transfer-to-logistics", {
|
|
body: {
|
|
orderId: "order-1",
|
|
reason: "no_response",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("reports delivery result", async () => {
|
|
invoke.mockResolvedValueOnce({
|
|
data: { ok: true },
|
|
error: null,
|
|
});
|
|
|
|
await expect(
|
|
reportDeliveryResult({
|
|
orderId: "order-1",
|
|
result: "delivered",
|
|
note: "Передано клиенту",
|
|
}),
|
|
).resolves.toEqual({ ok: true });
|
|
|
|
expect(invoke).toHaveBeenCalledWith("report-delivery-result", {
|
|
body: {
|
|
orderId: "order-1",
|
|
result: "delivered",
|
|
note: "Передано клиенту",
|
|
},
|
|
});
|
|
});
|
|
});
|