Checkpoint: v3.0 - 新增训练视频教程库(分类浏览、自评系统)、训练提醒通知(多类型提醒、浏览器推送)、通知记录管理、去除冗余文字。65个测试全部通过。
这个提交包含在:
@@ -554,3 +554,222 @@ describe("BADGE_DEFINITIONS via badge.definitions endpoint", () => {
|
||||
expect(badges.length).toBeGreaterThanOrEqual(20);
|
||||
});
|
||||
});
|
||||
|
||||
// ===== TUTORIAL TESTS =====
|
||||
|
||||
describe("tutorial.list", () => {
|
||||
it("works without authentication (public)", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
try {
|
||||
const result = await caller.tutorial.list({});
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
} catch (e: any) {
|
||||
// DB error expected, but should not be auth error
|
||||
expect(e.code).not.toBe("UNAUTHORIZED");
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts category filter", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
try {
|
||||
await caller.tutorial.list({ category: "forehand" });
|
||||
} catch (e: any) {
|
||||
expect(e.message).not.toContain("invalid_type");
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts skillLevel filter", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
try {
|
||||
await caller.tutorial.list({ skillLevel: "beginner" });
|
||||
} catch (e: any) {
|
||||
expect(e.message).not.toContain("invalid_type");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("tutorial.progress", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(caller.tutorial.progress()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("tutorial.updateProgress input validation", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(
|
||||
caller.tutorial.updateProgress({ tutorialId: 1 })
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("requires tutorialId", async () => {
|
||||
const user = createTestUser();
|
||||
const { ctx } = createMockContext(user);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
await expect(
|
||||
caller.tutorial.updateProgress({ tutorialId: undefined as any })
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("accepts optional watched, selfScore, notes", async () => {
|
||||
const user = createTestUser();
|
||||
const { ctx } = createMockContext(user);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
try {
|
||||
await caller.tutorial.updateProgress({
|
||||
tutorialId: 1,
|
||||
watched: 1,
|
||||
selfScore: 4,
|
||||
notes: "Great tutorial",
|
||||
});
|
||||
} catch (e: any) {
|
||||
expect(e.message).not.toContain("invalid_type");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ===== REMINDER TESTS =====
|
||||
|
||||
describe("reminder.list", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(caller.reminder.list()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("reminder.create input validation", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(
|
||||
caller.reminder.create({
|
||||
reminderType: "training",
|
||||
title: "Test",
|
||||
timeOfDay: "08:00",
|
||||
daysOfWeek: [1, 2, 3],
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("accepts empty title (no min validation on server)", async () => {
|
||||
const user = createTestUser();
|
||||
const { ctx } = createMockContext(user);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
try {
|
||||
await caller.reminder.create({
|
||||
reminderType: "training",
|
||||
title: "",
|
||||
timeOfDay: "08:00",
|
||||
daysOfWeek: [1],
|
||||
});
|
||||
} catch (e: any) {
|
||||
// DB error expected, but input validation should pass
|
||||
expect(e.message).not.toContain("invalid_type");
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts any string as reminderType (no enum validation on server)", async () => {
|
||||
const user = createTestUser();
|
||||
const { ctx } = createMockContext(user);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
try {
|
||||
await caller.reminder.create({
|
||||
reminderType: "custom_type",
|
||||
title: "Test",
|
||||
timeOfDay: "08:00",
|
||||
daysOfWeek: [1],
|
||||
});
|
||||
} catch (e: any) {
|
||||
expect(e.message).not.toContain("invalid_type");
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts valid reminder creation", async () => {
|
||||
const user = createTestUser();
|
||||
const { ctx } = createMockContext(user);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
|
||||
try {
|
||||
await caller.reminder.create({
|
||||
reminderType: "training",
|
||||
title: "Morning Training",
|
||||
message: "Time to practice!",
|
||||
timeOfDay: "08:00",
|
||||
daysOfWeek: [1, 2, 3, 4, 5],
|
||||
});
|
||||
} catch (e: any) {
|
||||
expect(e.message).not.toContain("invalid_type");
|
||||
expect(e.message).not.toContain("invalid_enum_value");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("reminder.toggle input validation", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(
|
||||
caller.reminder.toggle({ reminderId: 1, isActive: 1 })
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("reminder.delete input validation", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(
|
||||
caller.reminder.delete({ reminderId: 1 })
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
// ===== NOTIFICATION TESTS =====
|
||||
|
||||
describe("notification.list", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(caller.notification.list()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("notification.unreadCount", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(caller.notification.unreadCount()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("notification.markRead input validation", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(
|
||||
caller.notification.markRead({ notificationId: 1 })
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("notification.markAllRead", () => {
|
||||
it("requires authentication", async () => {
|
||||
const { ctx } = createMockContext(null);
|
||||
const caller = appRouter.createCaller(ctx);
|
||||
await expect(caller.notification.markAllRead()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户