2026-06-19 00:53:13 +08:00
|
|
|
import { describe, expect, test } from "vitest";
|
|
|
|
|
import { runActivityCheck } from "../app/services/activity.server";
|
|
|
|
|
import type { ActivityDeps, PushPayload } from "../app/services/activity.server";
|
|
|
|
|
|
|
|
|
|
function baseDeps(overrides: Partial<ActivityDeps> = {}): ActivityDeps {
|
|
|
|
|
return {
|
|
|
|
|
nowMs: () => 1776901800000,
|
|
|
|
|
uuid: () => "00000000-0000-4000-8000-000000000000",
|
|
|
|
|
getOrder: async () => ({
|
|
|
|
|
id: "gid://shopify/Order/123",
|
|
|
|
|
legacyId: "123",
|
|
|
|
|
orderNumber: "#1001",
|
|
|
|
|
email: "customer@example.com",
|
|
|
|
|
customerId: "99999",
|
|
|
|
|
currency: "USD",
|
|
|
|
|
shopDomain: "demo.myshopify.com",
|
|
|
|
|
}),
|
|
|
|
|
getConfig: async () => ({
|
|
|
|
|
activityId: 12,
|
|
|
|
|
apiAppId: "app-id",
|
|
|
|
|
apiSecret: "secret",
|
|
|
|
|
statusApiUrl: "https://third.example/status",
|
|
|
|
|
pushApiUrl: "https://third.example/push",
|
|
|
|
|
redirectUrl: "https://activity.example.com",
|
|
|
|
|
buttonText: { en: "Claim Your Reward", "zh-CN": "领取奖励" },
|
|
|
|
|
shop: "configured-shop",
|
|
|
|
|
}),
|
|
|
|
|
findSuccess: async () => null,
|
|
|
|
|
recordSuccess: async () => undefined,
|
|
|
|
|
log: async () => undefined,
|
|
|
|
|
callStatus: async () => ({
|
|
|
|
|
httpOk: true,
|
|
|
|
|
status: 200,
|
|
|
|
|
body: { code: 0, data: { status: true, trigger: ["card_add"] } },
|
|
|
|
|
requestBody: '{"activity_id":12}',
|
|
|
|
|
responseBody: '{"code":0,"data":{"status":true}}',
|
|
|
|
|
}),
|
|
|
|
|
callPush: async () => ({
|
|
|
|
|
httpOk: true,
|
|
|
|
|
status: 200,
|
|
|
|
|
body: { code: 0 },
|
|
|
|
|
requestBody: "{}",
|
|
|
|
|
responseBody: '{"code":0}',
|
|
|
|
|
}),
|
|
|
|
|
...overrides,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe("activity flow", () => {
|
|
|
|
|
test("returns false when status api says activity is inactive", async () => {
|
|
|
|
|
let pushCalls = 0;
|
|
|
|
|
const result = await runActivityCheck(
|
|
|
|
|
{ shop: "demo.myshopify.com", orderId: "123", orderNumber: "#1001", locale: "en" },
|
|
|
|
|
baseDeps({
|
|
|
|
|
callStatus: async () => ({
|
|
|
|
|
httpOk: true,
|
|
|
|
|
status: 200,
|
|
|
|
|
body: { code: 0, data: { status: false, trigger: ["order_end"] } },
|
|
|
|
|
requestBody: '{"activity_id":12}',
|
|
|
|
|
responseBody: '{"code":0,"data":{"status":false}}',
|
|
|
|
|
}),
|
|
|
|
|
callPush: async () => {
|
|
|
|
|
pushCalls += 1;
|
|
|
|
|
return { httpOk: true, status: 200, body: { code: 0 }, requestBody: "{}", responseBody: "{}" };
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual({ success: false });
|
|
|
|
|
expect(pushCalls).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("returns false when status api code is not zero", async () => {
|
|
|
|
|
const result = await runActivityCheck(
|
|
|
|
|
{ shop: "demo.myshopify.com", orderId: "123", orderNumber: "#1001", locale: "en" },
|
|
|
|
|
baseDeps({
|
|
|
|
|
callStatus: async () => ({
|
|
|
|
|
httpOk: true,
|
|
|
|
|
status: 200,
|
|
|
|
|
body: { code: 401, data: {}, msg: "auth required" },
|
|
|
|
|
requestBody: '{"activity_id":12}',
|
|
|
|
|
responseBody: '{"code":401}',
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual({ success: false });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("pushes order_end with configured shop and no line items", async () => {
|
2026-06-19 01:06:20 +08:00
|
|
|
const capturedPayloads: PushPayload[] = [];
|
2026-06-19 00:53:13 +08:00
|
|
|
const result = await runActivityCheck(
|
|
|
|
|
{ shop: "demo.myshopify.com", orderId: "gid://shopify/Order/123", orderNumber: "#1001", locale: "zh-CN" },
|
|
|
|
|
baseDeps({
|
|
|
|
|
callPush: async (_config, payload) => {
|
2026-06-19 01:06:20 +08:00
|
|
|
capturedPayloads.push(payload);
|
2026-06-19 00:53:13 +08:00
|
|
|
return {
|
|
|
|
|
httpOk: true,
|
|
|
|
|
status: 200,
|
|
|
|
|
body: { code: 0 },
|
|
|
|
|
requestBody: JSON.stringify(payload),
|
|
|
|
|
responseBody: '{"code":0}',
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual({
|
|
|
|
|
success: true,
|
|
|
|
|
redirectUrl: "https://activity.example.com",
|
|
|
|
|
buttonText: "领取奖励",
|
|
|
|
|
});
|
2026-06-19 01:06:20 +08:00
|
|
|
expect(capturedPayloads).toHaveLength(1);
|
|
|
|
|
const captured = capturedPayloads[0];
|
2026-06-19 00:53:13 +08:00
|
|
|
expect(captured).toMatchObject({
|
|
|
|
|
activity_id: 12,
|
|
|
|
|
event_type: "order_end",
|
|
|
|
|
created_at: 1776901800000,
|
|
|
|
|
user_id: "99999",
|
|
|
|
|
email: "customer@example.com",
|
|
|
|
|
event_id: "00000000-0000-4000-8000-000000000000",
|
|
|
|
|
properties: {
|
|
|
|
|
shop: "configured-shop",
|
|
|
|
|
currency: "USD",
|
|
|
|
|
order_id: "123",
|
|
|
|
|
order_number: "#1001",
|
|
|
|
|
},
|
|
|
|
|
});
|
2026-06-19 01:06:20 +08:00
|
|
|
expect(captured.properties).not.toHaveProperty("lineItems");
|
|
|
|
|
expect(captured.event_id).toMatch(/^[0-9a-f-]{36}$/);
|
2026-06-19 00:53:13 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("returns false when push api fails", async () => {
|
|
|
|
|
const result = await runActivityCheck(
|
|
|
|
|
{ shop: "demo.myshopify.com", orderId: "123", orderNumber: "#1001", locale: "en" },
|
|
|
|
|
baseDeps({
|
|
|
|
|
callPush: async () => ({
|
|
|
|
|
httpOk: true,
|
|
|
|
|
status: 200,
|
|
|
|
|
body: { code: 500 },
|
|
|
|
|
requestBody: "{}",
|
|
|
|
|
responseBody: '{"code":500}',
|
|
|
|
|
}),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual({ success: false });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("does not push when email is missing", async () => {
|
|
|
|
|
let pushCalls = 0;
|
|
|
|
|
const result = await runActivityCheck(
|
|
|
|
|
{ shop: "demo.myshopify.com", orderId: "123", orderNumber: "#1001", locale: "en" },
|
|
|
|
|
baseDeps({
|
|
|
|
|
getOrder: async () => ({
|
|
|
|
|
id: "gid://shopify/Order/123",
|
|
|
|
|
legacyId: "123",
|
|
|
|
|
orderNumber: "#1001",
|
|
|
|
|
customerId: "99999",
|
|
|
|
|
currency: "USD",
|
|
|
|
|
shopDomain: "demo.myshopify.com",
|
|
|
|
|
}),
|
|
|
|
|
callPush: async () => {
|
|
|
|
|
pushCalls += 1;
|
|
|
|
|
return { httpOk: true, status: 200, body: { code: 0 }, requestBody: "{}", responseBody: "{}" };
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual({ success: false });
|
|
|
|
|
expect(pushCalls).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("does not push when customer id is missing", async () => {
|
|
|
|
|
let pushCalls = 0;
|
|
|
|
|
const result = await runActivityCheck(
|
|
|
|
|
{ shop: "demo.myshopify.com", orderId: "123", orderNumber: "#1001", locale: "en" },
|
|
|
|
|
baseDeps({
|
|
|
|
|
getOrder: async () => ({
|
|
|
|
|
id: "gid://shopify/Order/123",
|
|
|
|
|
legacyId: "123",
|
|
|
|
|
orderNumber: "#1001",
|
|
|
|
|
email: "customer@example.com",
|
|
|
|
|
currency: "USD",
|
|
|
|
|
shopDomain: "demo.myshopify.com",
|
|
|
|
|
}),
|
|
|
|
|
callPush: async () => {
|
|
|
|
|
pushCalls += 1;
|
|
|
|
|
return { httpOk: true, status: 200, body: { code: 0 }, requestBody: "{}", responseBody: "{}" };
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual({ success: false });
|
|
|
|
|
expect(pushCalls).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
});
|