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; function createTestUser(overrides?: Partial): 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>); 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", }); }); });