Checkpoint: v3.0 - 新增训练视频教程库(分类浏览、自评系统)、训练提醒通知(多类型提醒、浏览器推送)、通知记录管理、去除冗余文字。65个测试全部通过。

这个提交包含在:
Manus
2026-03-14 08:28:57 -04:00
父节点 2c418b482e
当前提交 27083d5af9
修改 19 个文件,包含 2856 行新增32 行删除

查看文件

@@ -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();
});
});