import { beforeEach, describe, expect, test, vi } from "vitest"; import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node"; const mocks = vi.hoisted(() => ({ checkout: vi.fn(), unauthenticatedAdmin: vi.fn(), runActivityCheck: vi.fn(), })); vi.mock("../app/shopify.server", () => ({ authenticate: { public: { checkout: mocks.checkout, }, }, unauthenticated: { admin: mocks.unauthenticatedAdmin, }, })); vi.mock("../app/services/activity.server", () => ({ runActivityCheck: mocks.runActivityCheck, })); vi.mock("../app/services/config.server", () => ({ getActivityConfig: vi.fn(), })); vi.mock("../app/services/dreame-client.server", () => ({ callPush: vi.fn(), callStatus: vi.fn(), })); vi.mock("../app/services/idempotency.server", () => ({ findSuccessfulPush: vi.fn(), recordSuccessfulPush: vi.fn(), })); vi.mock("../app/services/logger.server", () => ({ logActivityPush: vi.fn(), })); vi.mock("../app/services/order.server", () => ({ getOrderDetails: vi.fn(), })); function cors(response: Response): Response { response.headers.set("X-Cors-Applied", "true"); return response; } async function postAction(body: unknown): Promise { const { action } = await import("../app/routes/api.action.activity.check"); return action({ request: new Request("https://app.example.com/api/action/activity/check", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), }), context: {}, params: {}, } as ActionFunctionArgs) as Promise; } describe("activity check route", () => { beforeEach(() => { vi.clearAllMocks(); mocks.checkout.mockResolvedValue({ sessionToken: { dest: "https://demo.myshopify.com", }, cors, }); mocks.unauthenticatedAdmin.mockResolvedValue({ admin: { graphql: vi.fn(), }, }); mocks.runActivityCheck.mockResolvedValue({ success: true, redirectUrl: "https://activity.example.com", buttonText: "Claim", }); }); test("wraps success responses with Shopify cors and no-store cache headers", async () => { const response = await postAction({ orderId: "gid://shopify/Order/123", orderNumber: "#1001", locale: "en", }); expect(response.status).toBe(200); expect(response.headers.get("X-Cors-Applied")).toBe("true"); expect(response.headers.get("Cache-Control")).toContain("no-store"); await expect(response.json()).resolves.toEqual({ success: true, redirectUrl: "https://activity.example.com", buttonText: "Claim", }); expect(mocks.runActivityCheck).toHaveBeenCalledWith( expect.objectContaining({ shop: "demo.myshopify.com", orderId: "gid://shopify/Order/123", }), expect.any(Object), ); }); test("rejects checkout tokens without a myshopify shop context", async () => { mocks.checkout.mockResolvedValue({ sessionToken: { dest: "https://not-shop.example.com", }, cors, }); const response = await postAction({ orderId: "gid://shopify/Order/123", }); expect(response.status).toBe(401); expect(response.headers.get("X-Cors-Applied")).toBe("true"); expect(response.headers.get("Cache-Control")).toContain("no-store"); await expect(response.json()).resolves.toEqual({ success: false, error: { code: "INVALID_SHOP_CONTEXT", message: "A valid Shopify shop context is required.", }, }); expect(mocks.unauthenticatedAdmin).not.toHaveBeenCalled(); expect(mocks.runActivityCheck).not.toHaveBeenCalled(); }); test("returns a bad request when orderId is missing", async () => { const response = await postAction({ orderNumber: "#1001", }); expect(response.status).toBe(400); expect(response.headers.get("X-Cors-Applied")).toBe("true"); expect(response.headers.get("Cache-Control")).toContain("no-store"); await expect(response.json()).resolves.toEqual({ success: false, error: { code: "ORDER_ID_REQUIRED", message: "orderId is required.", }, }); expect(mocks.runActivityCheck).not.toHaveBeenCalled(); }); test("preserves Shopify authentication failures as HTTP errors", async () => { mocks.checkout.mockRejectedValue(new Response("Unauthorized", { status: 401 })); const response = await postAction({ orderId: "gid://shopify/Order/123", }); expect(response.status).toBe(401); expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*"); expect(response.headers.get("Cache-Control")).toContain("no-store"); expect(await response.text()).toBe("Unauthorized"); expect(mocks.unauthenticatedAdmin).not.toHaveBeenCalled(); expect(mocks.runActivityCheck).not.toHaveBeenCalled(); }); test("uses Shopify checkout authentication for OPTIONS responses", async () => { const { loader } = await import("../app/routes/api.action.activity.check"); const response = await loader({ request: new Request("https://app.example.com/api/action/activity/check", { method: "OPTIONS", }), context: {}, params: {}, } as LoaderFunctionArgs); expect(response.status).toBe(204); expect(response.headers.get("X-Cors-Applied")).toBe("true"); expect(response.headers.get("Cache-Control")).toContain("no-store"); expect(mocks.checkout).toHaveBeenCalled(); }); });