Add market watch and match hub workflows

这个提交包含在:
cryptocommuniums-afk
2026-04-07 11:00:03 +08:00
父节点 495da60212
当前提交 32ffad1545
修改 39 个文件,包含 6974 行新增330 行删除

133
server/market.routes.test.ts 普通文件
查看文件

@@ -0,0 +1,133 @@
import { describe, expect, it, vi, afterEach } from "vitest";
import { appRouter } from "./routers";
import type { TrpcContext } from "./_core/context";
import * as db from "./db";
type AuthenticatedUser = NonNullable<TrpcContext["user"]>;
function createTestUser(overrides?: Partial<AuthenticatedUser>): AuthenticatedUser {
return {
id: 7,
openId: "market-user-7",
email: "market@example.com",
name: "MarketTester",
loginMethod: "username",
role: "user",
skillLevel: "beginner",
trainingGoals: null,
ntrpRating: 1.5,
manualNtrpRating: null,
manualNtrpCapturedAt: null,
heightCm: null,
weightKg: null,
sprintSpeedScore: null,
explosivePowerScore: null,
agilityScore: null,
enduranceScore: null,
flexibilityScore: null,
coreStabilityScore: null,
shoulderMobilityScore: null,
hipMobilityScore: null,
assessmentNotes: null,
totalSessions: 0,
totalMinutes: 0,
totalShots: 0,
currentStreak: 0,
longestStreak: 0,
createdAt: new Date(),
updatedAt: new Date(),
lastSignedIn: new Date(),
...overrides,
};
}
function createMockContext(user: AuthenticatedUser | null): TrpcContext {
return {
user,
sessionSid: user ? "market-session" : null,
req: {
protocol: "https",
headers: {},
} as TrpcContext["req"],
res: {
clearCookie: vi.fn(),
cookie: vi.fn(),
} as TrpcContext["res"],
};
}
afterEach(() => {
vi.restoreAllMocks();
});
describe("market.watchRuleCreate", () => {
it("creates a rule and queues a refresh task", async () => {
const user = createTestUser();
const caller = appRouter.createCaller(createMockContext(user));
vi.spyOn(db, "createRacketWatchRule").mockResolvedValueOnce(88);
vi.spyOn(db, "createBackgroundTask").mockResolvedValueOnce("task-market-1");
vi.spyOn(db, "getBackgroundTaskById").mockResolvedValueOnce({
id: "task-market-1",
userId: user.id,
type: "market_watch_refresh",
status: "queued",
title: "Yonex ≤ ¥500 刷新",
message: "监控规则已创建,后台开始抓取对应平台价格",
progress: 0,
payload: {},
result: null,
error: null,
attempts: 0,
maxAttempts: 3,
workerId: null,
runAfter: new Date(),
lockedAt: null,
startedAt: null,
completedAt: null,
createdAt: new Date(),
updatedAt: new Date(),
} as Awaited<ReturnType<typeof db.getBackgroundTaskById>>);
const result = await caller.market.watchRuleCreate({
brand: "Yonex",
targetPrice: 500,
modelKeyword: "98",
pushEnabled: true,
});
expect(result.ruleId).toBe(88);
expect(result.taskId).toBeTruthy();
expect(db.createRacketWatchRule).toHaveBeenCalledWith(expect.objectContaining({
userId: user.id,
brand: "Yonex",
targetPrice: 500,
pushEnabled: 1,
isActive: 1,
}));
expect(db.createBackgroundTask).toHaveBeenCalledWith(expect.objectContaining({
userId: user.id,
type: "market_watch_refresh",
}));
});
});
describe("market.pushConfigUpdate", () => {
it("updates the default webhook for admins", async () => {
const admin = createTestUser({ role: "admin", id: 1, name: "Admin" });
const caller = appRouter.createCaller(createMockContext(admin));
vi.spyOn(db, "updateAppSetting").mockResolvedValueOnce(undefined);
vi.spyOn(db, "createAdminAuditLog").mockResolvedValueOnce(undefined);
const result = await caller.market.pushConfigUpdate({
webhookUrl: "https://open.larksuite.com/open-apis/bot/v2/hook/demo",
});
expect(result).toEqual({ success: true });
expect(db.updateAppSetting).toHaveBeenCalledWith("market_default_feishu_webhook", {
value: "https://open.larksuite.com/open-apis/bot/v2/hook/demo",
type: "string",
});
});
});