@studious-lms/server 1.1.24 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.coderabbit.yaml +9 -0
- package/.env.example +53 -0
- package/.env.test.example +37 -0
- package/README.md +34 -7
- package/dist/exportType.d.ts.map +1 -1
- package/dist/exportType.js +4 -0
- package/dist/exportType.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +212 -51
- package/dist/index.js.map +1 -0
- package/dist/instrument.d.ts +2 -0
- package/dist/instrument.d.ts.map +1 -0
- package/dist/instrument.js +18 -0
- package/dist/instrument.js.map +1 -0
- package/dist/lib/config/env.d.ts +190 -0
- package/dist/lib/config/env.d.ts.map +1 -0
- package/dist/lib/config/env.js +121 -0
- package/dist/lib/config/env.js.map +1 -0
- package/dist/lib/fileUpload.d.ts +2 -2
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +82 -15
- package/dist/lib/fileUpload.js.map +1 -0
- package/dist/lib/googleCloudStorage.d.ts +13 -0
- package/dist/lib/googleCloudStorage.d.ts.map +1 -1
- package/dist/lib/googleCloudStorage.js +45 -6
- package/dist/lib/googleCloudStorage.js.map +1 -0
- package/dist/lib/jsonConversion.d.ts.map +1 -1
- package/dist/lib/jsonConversion.js +16 -14
- package/dist/lib/jsonConversion.js.map +1 -0
- package/dist/lib/jsonStyles.d.ts.map +1 -1
- package/dist/lib/jsonStyles.js +4 -0
- package/dist/lib/jsonStyles.js.map +1 -0
- package/dist/lib/notificationHandler.d.ts +25 -0
- package/dist/lib/notificationHandler.d.ts.map +1 -0
- package/dist/lib/notificationHandler.js +32 -0
- package/dist/lib/notificationHandler.js.map +1 -0
- package/dist/lib/prisma.d.ts +2 -2
- package/dist/lib/prisma.d.ts.map +1 -1
- package/dist/lib/prisma.js +24 -1
- package/dist/lib/prisma.js.map +1 -0
- package/dist/lib/pusher.d.ts +4 -1
- package/dist/lib/pusher.d.ts.map +1 -1
- package/dist/lib/pusher.js +14 -6
- package/dist/lib/pusher.js.map +1 -0
- package/dist/lib/redis.d.ts +5 -0
- package/dist/lib/redis.d.ts.map +1 -0
- package/dist/lib/redis.js +53 -0
- package/dist/lib/redis.js.map +1 -0
- package/dist/lib/thumbnailGenerator.d.ts +0 -21
- package/dist/lib/thumbnailGenerator.d.ts.map +1 -1
- package/dist/lib/thumbnailGenerator.js +159 -158
- package/dist/lib/thumbnailGenerator.js.map +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +41 -93
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/logging.d.ts.map +1 -1
- package/dist/middleware/logging.js +4 -0
- package/dist/middleware/logging.js.map +1 -0
- package/dist/middleware/security.d.ts +5 -0
- package/dist/middleware/security.d.ts.map +1 -0
- package/dist/middleware/security.js +77 -0
- package/dist/middleware/security.js.map +1 -0
- package/dist/models/agenda.d.ts +97 -0
- package/dist/models/agenda.d.ts.map +1 -0
- package/dist/models/agenda.js +40 -0
- package/dist/models/agenda.js.map +1 -0
- package/dist/models/announcement.d.ts +223 -0
- package/dist/models/announcement.d.ts.map +1 -0
- package/dist/models/announcement.js +120 -0
- package/dist/models/announcement.js.map +1 -0
- package/dist/models/assignment.d.ts +1292 -0
- package/dist/models/assignment.d.ts.map +1 -0
- package/dist/models/assignment.js +309 -0
- package/dist/models/assignment.js.map +1 -0
- package/dist/models/attendance.d.ts +180 -0
- package/dist/models/attendance.d.ts.map +1 -0
- package/dist/models/attendance.js +188 -0
- package/dist/models/attendance.js.map +1 -0
- package/dist/models/auth.d.ts +153 -0
- package/dist/models/auth.d.ts.map +1 -0
- package/dist/models/auth.js +217 -0
- package/dist/models/auth.js.map +1 -0
- package/dist/models/class.d.ts +439 -0
- package/dist/models/class.d.ts.map +1 -0
- package/dist/models/class.js +546 -0
- package/dist/models/class.js.map +1 -0
- package/dist/models/comment.d.ts +171 -0
- package/dist/models/comment.d.ts.map +1 -0
- package/dist/models/comment.js +138 -0
- package/dist/models/comment.js.map +1 -0
- package/dist/models/conversation.d.ts +164 -0
- package/dist/models/conversation.d.ts.map +1 -0
- package/dist/models/conversation.js +175 -0
- package/dist/models/conversation.js.map +1 -0
- package/dist/models/event.d.ts +295 -0
- package/dist/models/event.d.ts.map +1 -0
- package/dist/models/event.js +145 -0
- package/dist/models/event.js.map +1 -0
- package/dist/models/file.d.ts +536 -0
- package/dist/models/file.d.ts.map +1 -0
- package/dist/models/file.js +126 -0
- package/dist/models/file.js.map +1 -0
- package/dist/models/folder.d.ts +295 -0
- package/dist/models/folder.d.ts.map +1 -0
- package/dist/models/folder.js +202 -0
- package/dist/models/folder.js.map +1 -0
- package/dist/models/labChat.d.ts +243 -0
- package/dist/models/labChat.d.ts.map +1 -0
- package/dist/models/labChat.js +204 -0
- package/dist/models/labChat.js.map +1 -0
- package/dist/models/marketing.d.ts +72 -0
- package/dist/models/marketing.d.ts.map +1 -0
- package/dist/models/marketing.js +26 -0
- package/dist/models/marketing.js.map +1 -0
- package/dist/models/message.d.ts +100 -0
- package/dist/models/message.d.ts.map +1 -0
- package/dist/models/message.js +131 -0
- package/dist/models/message.js.map +1 -0
- package/dist/models/newtonChat.d.ts +72 -0
- package/dist/models/newtonChat.d.ts.map +1 -0
- package/dist/models/newtonChat.js +61 -0
- package/dist/models/newtonChat.js.map +1 -0
- package/dist/models/notification.d.ts +65 -0
- package/dist/models/notification.d.ts.map +1 -0
- package/dist/models/notification.js +46 -0
- package/dist/models/notification.js.map +1 -0
- package/dist/models/section.d.ts +102 -0
- package/dist/models/section.d.ts.map +1 -0
- package/dist/models/section.js +83 -0
- package/dist/models/section.js.map +1 -0
- package/dist/models/user.d.ts +39 -0
- package/dist/models/user.d.ts.map +1 -0
- package/dist/models/user.js +38 -0
- package/dist/models/user.js.map +1 -0
- package/dist/models/worksheet.d.ts +460 -0
- package/dist/models/worksheet.d.ts.map +1 -0
- package/dist/models/worksheet.js +200 -0
- package/dist/models/worksheet.js.map +1 -0
- package/dist/pipelines/aiLabChat.d.ts +21 -0
- package/dist/pipelines/aiLabChat.d.ts.map +1 -0
- package/dist/pipelines/aiLabChat.js +460 -0
- package/dist/pipelines/aiLabChat.js.map +1 -0
- package/dist/pipelines/aiNewtonChat.d.ts +30 -0
- package/dist/pipelines/aiNewtonChat.d.ts.map +1 -0
- package/dist/pipelines/aiNewtonChat.js +289 -0
- package/dist/pipelines/aiNewtonChat.js.map +1 -0
- package/dist/pipelines/gradeWorksheet.d.ts +30 -0
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -0
- package/dist/pipelines/gradeWorksheet.js +252 -0
- package/dist/pipelines/gradeWorksheet.js.map +1 -0
- package/dist/routers/_app.d.ts +6403 -3741
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +10 -0
- package/dist/routers/_app.js.map +1 -0
- package/dist/routers/agenda.d.ts +58 -6
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/agenda.js +6 -58
- package/dist/routers/agenda.js.map +1 -0
- package/dist/routers/announcement.d.ts +325 -6
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/announcement.js +547 -57
- package/dist/routers/announcement.js.map +1 -0
- package/dist/routers/assignment.d.ts +431 -318
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +104 -1559
- package/dist/routers/assignment.js.map +1 -0
- package/dist/routers/attendance.d.ts +20 -9
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +10 -263
- package/dist/routers/attendance.js.map +1 -0
- package/dist/routers/auth.d.ts +21 -1
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/auth.js +37 -241
- package/dist/routers/auth.js.map +1 -0
- package/dist/routers/class.d.ts +198 -68
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +88 -909
- package/dist/routers/class.js.map +1 -0
- package/dist/routers/comment.d.ts +153 -0
- package/dist/routers/comment.d.ts.map +1 -0
- package/dist/routers/comment.js +58 -0
- package/dist/routers/comment.js.map +1 -0
- package/dist/routers/conversation.d.ts +73 -3
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/conversation.js +23 -265
- package/dist/routers/conversation.js.map +1 -0
- package/dist/routers/event.d.ts +46 -37
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/event.js +15 -431
- package/dist/routers/event.js.map +1 -0
- package/dist/routers/file.d.ts +4 -2
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/file.js +11 -295
- package/dist/routers/file.js.map +1 -0
- package/dist/routers/folder.d.ts +21 -14
- package/dist/routers/folder.d.ts.map +1 -1
- package/dist/routers/folder.js +36 -743
- package/dist/routers/folder.js.map +1 -0
- package/dist/routers/labChat.d.ts +12 -9
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/labChat.js +21 -877
- package/dist/routers/labChat.js.map +1 -0
- package/dist/routers/marketing.d.ts +2 -2
- package/dist/routers/marketing.d.ts.map +1 -1
- package/dist/routers/marketing.js +9 -54
- package/dist/routers/marketing.js.map +1 -0
- package/dist/routers/message.d.ts +2 -1
- package/dist/routers/message.d.ts.map +1 -1
- package/dist/routers/message.js +29 -519
- package/dist/routers/message.js.map +1 -0
- package/dist/routers/newtonChat.d.ts +55 -0
- package/dist/routers/newtonChat.d.ts.map +1 -0
- package/dist/routers/newtonChat.js +22 -0
- package/dist/routers/newtonChat.js.map +1 -0
- package/dist/routers/notifications.d.ts +8 -8
- package/dist/routers/notifications.d.ts.map +1 -1
- package/dist/routers/notifications.js +20 -81
- package/dist/routers/notifications.js.map +1 -0
- package/dist/routers/section.d.ts +37 -6
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +26 -167
- package/dist/routers/section.js.map +1 -0
- package/dist/routers/user.d.ts +1 -1
- package/dist/routers/user.d.ts.map +1 -1
- package/dist/routers/user.js +34 -204
- package/dist/routers/user.js.map +1 -0
- package/dist/routers/worksheet.d.ts +362 -0
- package/dist/routers/worksheet.d.ts.map +1 -0
- package/dist/routers/worksheet.js +153 -0
- package/dist/routers/worksheet.js.map +1 -0
- package/dist/seedDatabase.d.ts +2 -3
- package/dist/seedDatabase.d.ts.map +1 -1
- package/dist/seedDatabase.js +311 -289
- package/dist/seedDatabase.js.map +1 -0
- package/dist/server/pipelines/aiLabChat.d.ts +21 -0
- package/dist/server/pipelines/aiLabChat.d.ts.map +1 -0
- package/dist/server/pipelines/aiLabChat.js +456 -0
- package/dist/server/pipelines/aiLabChat.js.map +1 -0
- package/dist/server/pipelines/aiNewtonChat.d.ts +30 -0
- package/dist/server/pipelines/aiNewtonChat.d.ts.map +1 -0
- package/dist/server/pipelines/aiNewtonChat.js +285 -0
- package/dist/server/pipelines/aiNewtonChat.js.map +1 -0
- package/dist/server/pipelines/gradeWorksheet.d.ts +30 -0
- package/dist/server/pipelines/gradeWorksheet.d.ts.map +1 -0
- package/dist/server/pipelines/gradeWorksheet.js +248 -0
- package/dist/server/pipelines/gradeWorksheet.js.map +1 -0
- package/dist/services/agenda.d.ts +100 -0
- package/dist/services/agenda.d.ts.map +1 -0
- package/dist/services/agenda.js +21 -0
- package/dist/services/agenda.js.map +1 -0
- package/dist/services/announcement.d.ts +135 -0
- package/dist/services/announcement.d.ts.map +1 -0
- package/dist/services/announcement.js +223 -0
- package/dist/services/announcement.js.map +1 -0
- package/dist/services/assignment.d.ts +1462 -0
- package/dist/services/assignment.d.ts.map +1 -0
- package/dist/services/assignment.js +898 -0
- package/dist/services/assignment.js.map +1 -0
- package/dist/services/attendance.d.ts +93 -0
- package/dist/services/attendance.d.ts.map +1 -0
- package/dist/services/attendance.js +61 -0
- package/dist/services/attendance.js.map +1 -0
- package/dist/services/auth.d.ts +68 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +218 -0
- package/dist/services/auth.js.map +1 -0
- package/dist/services/class.d.ts +621 -0
- package/dist/services/class.d.ts.map +1 -0
- package/dist/services/class.js +474 -0
- package/dist/services/class.js.map +1 -0
- package/dist/services/comment.d.ts +100 -0
- package/dist/services/comment.d.ts.map +1 -0
- package/dist/services/comment.js +83 -0
- package/dist/services/comment.js.map +1 -0
- package/dist/services/conversation.d.ts +159 -0
- package/dist/services/conversation.d.ts.map +1 -0
- package/dist/services/conversation.js +138 -0
- package/dist/services/conversation.js.map +1 -0
- package/dist/services/event.d.ts +216 -0
- package/dist/services/event.d.ts.map +1 -0
- package/dist/services/event.js +168 -0
- package/dist/services/event.js.map +1 -0
- package/dist/services/file.d.ts +74 -0
- package/dist/services/file.d.ts.map +1 -0
- package/dist/services/file.js +133 -0
- package/dist/services/file.js.map +1 -0
- package/dist/services/folder.d.ts +239 -0
- package/dist/services/folder.d.ts.map +1 -0
- package/dist/services/folder.js +248 -0
- package/dist/services/folder.js.map +1 -0
- package/dist/services/labChat.d.ts +165 -0
- package/dist/services/labChat.d.ts.map +1 -0
- package/dist/services/labChat.js +289 -0
- package/dist/services/labChat.js.map +1 -0
- package/dist/services/marketing.d.ts +50 -0
- package/dist/services/marketing.d.ts.map +1 -0
- package/dist/services/marketing.js +32 -0
- package/dist/services/marketing.js.map +1 -0
- package/dist/services/message.d.ts +95 -0
- package/dist/services/message.d.ts.map +1 -0
- package/dist/services/message.js +350 -0
- package/dist/services/message.js.map +1 -0
- package/dist/services/newtonChat.d.ts +22 -0
- package/dist/services/newtonChat.d.ts.map +1 -0
- package/dist/services/newtonChat.js +174 -0
- package/dist/services/newtonChat.js.map +1 -0
- package/dist/services/notification.d.ts +65 -0
- package/dist/services/notification.d.ts.map +1 -0
- package/dist/services/notification.js +33 -0
- package/dist/services/notification.js.map +1 -0
- package/dist/services/section.d.ts +53 -0
- package/dist/services/section.d.ts.map +1 -0
- package/dist/services/section.js +199 -0
- package/dist/services/section.js.map +1 -0
- package/dist/services/user.d.ts +48 -0
- package/dist/services/user.d.ts.map +1 -0
- package/dist/services/user.js +141 -0
- package/dist/services/user.js.map +1 -0
- package/dist/services/worksheet.d.ts +239 -0
- package/dist/services/worksheet.d.ts.map +1 -0
- package/dist/services/worksheet.js +235 -0
- package/dist/services/worksheet.js.map +1 -0
- package/dist/socket/handlers.d.ts.map +1 -1
- package/dist/socket/handlers.js +4 -0
- package/dist/socket/handlers.js.map +1 -0
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +4 -0
- package/dist/trpc.js.map +1 -0
- package/dist/types/trpc.d.ts.map +1 -1
- package/dist/types/trpc.js +4 -0
- package/dist/types/trpc.js.map +1 -0
- package/dist/utils/aiUser.d.ts +1 -3
- package/dist/utils/aiUser.d.ts.map +1 -1
- package/dist/utils/aiUser.js +8 -3
- package/dist/utils/aiUser.js.map +1 -0
- package/dist/utils/email.d.ts +12 -1
- package/dist/utils/email.d.ts.map +1 -1
- package/dist/utils/email.js +26 -4
- package/dist/utils/email.js.map +1 -0
- package/dist/utils/generateInviteCode.d.ts +1 -2
- package/dist/utils/generateInviteCode.d.ts.map +1 -1
- package/dist/utils/generateInviteCode.js +5 -2
- package/dist/utils/generateInviteCode.js.map +1 -0
- package/dist/utils/inference.d.ts +8 -0
- package/dist/utils/inference.d.ts.map +1 -1
- package/dist/utils/inference.js +78 -10
- package/dist/utils/inference.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +35 -3
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prismaErrorHandler.d.ts.map +1 -1
- package/dist/utils/prismaErrorHandler.js +7 -0
- package/dist/utils/prismaErrorHandler.js.map +1 -0
- package/dist/utils/prismaWrapper.d.ts +1 -0
- package/dist/utils/prismaWrapper.d.ts.map +1 -1
- package/dist/utils/prismaWrapper.js +8 -0
- package/dist/utils/prismaWrapper.js.map +1 -0
- package/docker-compose.yml +19 -0
- package/package.json +21 -4
- package/prisma/migrations/20251109122857_annuoncements_comments/migration.sql +30 -0
- package/prisma/migrations/20251109135555_reactions_announcements_comments/migration.sql +35 -0
- package/prisma/schema.prisma +180 -12
- package/scripts/test-pre-push.ts +14 -0
- package/src/index.ts +247 -52
- package/src/instrument.ts +15 -0
- package/src/lib/config/env.ts +132 -0
- package/src/lib/fileUpload.ts +81 -16
- package/src/lib/googleCloudStorage.ts +42 -6
- package/src/lib/jsonConversion.ts +12 -14
- package/src/lib/prisma.ts +23 -2
- package/src/lib/pusher.ts +11 -6
- package/src/lib/redis.ts +56 -0
- package/src/lib/thumbnailGenerator.ts +170 -168
- package/src/middleware/auth.ts +86 -137
- package/src/middleware/security.ts +80 -0
- package/src/models/agenda.ts +46 -0
- package/src/models/announcement.ts +134 -0
- package/src/models/assignment.ts +322 -0
- package/src/models/attendance.ts +208 -0
- package/src/models/auth.ts +247 -0
- package/src/models/class.ts +598 -0
- package/src/models/comment.ts +152 -0
- package/src/models/conversation.ts +200 -0
- package/src/models/event.ts +177 -0
- package/src/models/file.ts +129 -0
- package/src/models/folder.ts +225 -0
- package/src/models/labChat.ts +213 -0
- package/src/models/marketing.ts +45 -0
- package/src/models/message.ts +153 -0
- package/src/models/newtonChat.ts +70 -0
- package/src/models/notification.ts +54 -0
- package/src/models/section.ts +98 -0
- package/src/models/user.ts +47 -0
- package/src/models/worksheet.ts +294 -0
- package/src/pipelines/aiLabChat.ts +511 -0
- package/src/pipelines/aiNewtonChat.ts +347 -0
- package/src/pipelines/gradeWorksheet.ts +286 -0
- package/src/routers/_app.ts +6 -0
- package/src/routers/agenda.ts +3 -61
- package/src/routers/announcement.ts +622 -57
- package/src/routers/assignment.ts +157 -1688
- package/src/routers/attendance.ts +16 -277
- package/src/routers/auth.ts +79 -313
- package/src/routers/class.ts +265 -1038
- package/src/routers/comment.ts +76 -0
- package/src/routers/conversation.ts +53 -284
- package/src/routers/event.ts +50 -481
- package/src/routers/file.ts +45 -341
- package/src/routers/folder.ts +107 -836
- package/src/routers/labChat.ts +29 -960
- package/src/routers/marketing.ts +35 -77
- package/src/routers/message.ts +45 -571
- package/src/routers/newtonChat.ts +36 -0
- package/src/routers/notifications.ts +32 -82
- package/src/routers/section.ts +58 -200
- package/src/routers/user.ts +49 -226
- package/src/routers/worksheet.ts +252 -0
- package/src/seedDatabase.ts +330 -290
- package/src/services/agenda.ts +21 -0
- package/src/services/announcement.ts +290 -0
- package/src/services/assignment.ts +1198 -0
- package/src/services/attendance.ts +85 -0
- package/src/services/auth.ts +277 -0
- package/src/services/class.ts +622 -0
- package/src/services/comment.ts +106 -0
- package/src/services/conversation.ts +213 -0
- package/src/services/event.ts +231 -0
- package/src/services/file.ts +167 -0
- package/src/services/folder.ts +316 -0
- package/src/services/labChat.ts +352 -0
- package/src/services/marketing.ts +57 -0
- package/src/services/message.ts +461 -0
- package/src/services/newtonChat.ts +222 -0
- package/src/services/notification.ts +50 -0
- package/src/services/section.ts +283 -0
- package/src/services/user.ts +172 -0
- package/src/services/worksheet.ts +358 -0
- package/src/trpc.ts +4 -0
- package/src/utils/aiUser.ts +4 -3
- package/src/utils/email.ts +33 -4
- package/src/utils/generateInviteCode.ts +1 -3
- package/src/utils/inference.ts +89 -10
- package/src/utils/logger.ts +33 -3
- package/src/utils/prismaErrorHandler.ts +3 -0
- package/src/utils/prismaWrapper.ts +4 -0
- package/tests/globalSetup.ts +62 -0
- package/tests/helpers.ts +22 -0
- package/tests/middleware/security.test.ts +42 -0
- package/tests/routers/agenda.test.ts +138 -0
- package/tests/routers/announcement.test.ts +490 -0
- package/tests/routers/assignment.test.ts +837 -0
- package/tests/routers/attendance.test.ts +160 -0
- package/tests/routers/auth.test.ts +171 -0
- package/tests/{class.test.ts → routers/class.test.ts} +163 -92
- package/tests/routers/comment.test.ts +126 -0
- package/tests/routers/conversation.test.ts +145 -0
- package/tests/routers/event.test.ts +289 -0
- package/tests/routers/folder.test.ts +178 -0
- package/tests/routers/labChat.test.ts +115 -0
- package/tests/routers/marketing.test.ts +59 -0
- package/tests/routers/message.test.ts +123 -0
- package/tests/routers/notification.test.ts +69 -0
- package/tests/routers/section.test.ts +208 -0
- package/tests/server/rateLimit.test.ts +73 -0
- package/tests/setup.ts +39 -65
- package/tests/user.test.ts +136 -0
- package/tests/utils/aiUser.test.ts +22 -0
- package/tests/utils/generateInviteCode.test.ts +24 -0
- package/tests/utils/logger.test.ts +74 -0
- package/tests/utils/prismaErrorHandler.test.ts +101 -0
- package/tests/utils/prismaWrapper.test.ts +82 -0
- package/tests/worksheet.test.ts +181 -0
- package/tsconfig.json +9 -2
- package/vitest.config.ts +30 -1
- package/vitest.unit.config.ts +21 -0
- package/API_SPECIFICATION.md +0 -1597
- package/BASE64_REMOVAL_SUMMARY.md +0 -164
- package/CHAT_API_SPEC.md +0 -579
- package/LAB_CHAT_API_SPEC.md +0 -518
- package/dist/routers/school.d.ts +0 -208
- package/dist/routers/school.d.ts.map +0 -1
- package/dist/routers/school.js +0 -481
- package/tests/auth.test.ts +0 -25
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach } from 'vitest';
|
|
2
|
+
import { user1Caller, user2Caller } from './setup';
|
|
3
|
+
import { expectUnauth } from './helpers';
|
|
4
|
+
|
|
5
|
+
describe('User Router', () => {
|
|
6
|
+
describe('getProfile', () => {
|
|
7
|
+
test('should get user profile successfully', async () => {
|
|
8
|
+
const profile = await user1Caller.user.getProfile();
|
|
9
|
+
|
|
10
|
+
expect(profile).toBeDefined();
|
|
11
|
+
expect(profile.id).toBeDefined();
|
|
12
|
+
expect(profile.username).toBe('testuser1');
|
|
13
|
+
expect(profile.profile).toBeDefined();
|
|
14
|
+
expect(profile.profile.displayName).toBeNull();
|
|
15
|
+
expect(profile.profile.bio).toBeNull();
|
|
16
|
+
expect(profile.profile.location).toBeNull();
|
|
17
|
+
expect(profile.profile.website).toBeNull();
|
|
18
|
+
expect(profile.profile.profilePicture).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('should fail without authentication', () =>
|
|
22
|
+
expectUnauth((c) => c.user.getProfile()));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('updateProfile', () => {
|
|
26
|
+
test('should update profile with display name and bio', async () => {
|
|
27
|
+
const updated = await user1Caller.user.updateProfile({
|
|
28
|
+
profile: {
|
|
29
|
+
displayName: 'Test User One',
|
|
30
|
+
bio: 'This is a test bio',
|
|
31
|
+
location: 'Test City',
|
|
32
|
+
website: 'https://example.com',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect(updated).toBeDefined();
|
|
37
|
+
expect(updated.profile.displayName).toBe('Test User One');
|
|
38
|
+
expect(updated.profile.bio).toBe('This is a test bio');
|
|
39
|
+
expect(updated.profile.location).toBe('Test City');
|
|
40
|
+
expect(updated.profile.website).toBe('https://example.com');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('should update profile with partial data', async () => {
|
|
44
|
+
const updated = await user2Caller.user.updateProfile({
|
|
45
|
+
profile: {
|
|
46
|
+
displayName: 'Test User Two',
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(updated.profile.displayName).toBe('Test User Two');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('should update profile with DiceBear avatar', async () => {
|
|
54
|
+
const updated = await user1Caller.user.updateProfile({
|
|
55
|
+
dicebearAvatar: {
|
|
56
|
+
url: 'https://api.dicebear.com/7.x/avataaars/svg?seed=test',
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(updated.profile.profilePicture).toBe('https://api.dicebear.com/7.x/avataaars/svg?seed=test');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// test('should clear profile fields when set to null', async () => {
|
|
64
|
+
// // First set some values
|
|
65
|
+
// await user1Caller.user.updateProfile({
|
|
66
|
+
// profile: {
|
|
67
|
+
// displayName: 'Test Name',
|
|
68
|
+
// bio: 'Test Bio',
|
|
69
|
+
// },
|
|
70
|
+
// });
|
|
71
|
+
|
|
72
|
+
// // Then clear them
|
|
73
|
+
// const cleared = await user1Caller.user.updateProfile({
|
|
74
|
+
// profile: {
|
|
75
|
+
// displayName: null,
|
|
76
|
+
// bio: null,
|
|
77
|
+
// },
|
|
78
|
+
// });
|
|
79
|
+
|
|
80
|
+
// expect(cleared.profile.displayName).toBeNull();
|
|
81
|
+
// expect(cleared.profile.bio).toBeNull();
|
|
82
|
+
// });
|
|
83
|
+
|
|
84
|
+
test('should fail without authentication', () =>
|
|
85
|
+
expectUnauth((c) => c.user.updateProfile({
|
|
86
|
+
profile: { displayName: 'Test' },
|
|
87
|
+
})));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('getUploadUrl', () => {
|
|
91
|
+
test('should generate upload URL for valid image file', async () => {
|
|
92
|
+
const result = await user1Caller.user.getUploadUrl({
|
|
93
|
+
fileName: 'profile.jpg',
|
|
94
|
+
fileType: 'image/jpeg',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(result).toBeDefined();
|
|
98
|
+
expect(result.uploadUrl).toBeDefined();
|
|
99
|
+
expect(result.filePath).toBeDefined();
|
|
100
|
+
expect(result.fileName).toBeDefined();
|
|
101
|
+
expect(result.filePath).toContain('users/');
|
|
102
|
+
expect(result.filePath).toContain('/profile/');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should generate upload URL for PNG file', async () => {
|
|
106
|
+
const result = await user1Caller.user.getUploadUrl({
|
|
107
|
+
fileName: 'avatar.png',
|
|
108
|
+
fileType: 'image/png',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(result).toBeDefined();
|
|
112
|
+
expect(result.fileName).toContain('.png');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('should fail for invalid file type', async () => {
|
|
116
|
+
await expect(user1Caller.user.getUploadUrl({
|
|
117
|
+
fileName: 'document.pdf',
|
|
118
|
+
fileType: 'application/pdf',
|
|
119
|
+
})).rejects.toThrow();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('should fail for empty file name', async () => {
|
|
123
|
+
await expect(user1Caller.user.getUploadUrl({
|
|
124
|
+
fileName: '',
|
|
125
|
+
fileType: 'image/jpeg',
|
|
126
|
+
})).rejects.toThrow();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('should fail without authentication', () =>
|
|
130
|
+
expectUnauth((c) => c.user.getUploadUrl({
|
|
131
|
+
fileName: 'test.jpg',
|
|
132
|
+
fileType: 'image/jpeg',
|
|
133
|
+
})));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { getAIUserId, isAIUser } from '../../src/utils/aiUser.js';
|
|
3
|
+
|
|
4
|
+
describe('aiUser utils', () => {
|
|
5
|
+
describe('getAIUserId', () => {
|
|
6
|
+
test('returns AI_ASSISTANT constant', () => {
|
|
7
|
+
expect(getAIUserId()).toBe('AI_ASSISTANT');
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('isAIUser', () => {
|
|
12
|
+
test('returns true for AI_ASSISTANT', () => {
|
|
13
|
+
expect(isAIUser('AI_ASSISTANT')).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('returns false for other user ids', () => {
|
|
17
|
+
expect(isAIUser('user-123')).toBe(false);
|
|
18
|
+
expect(isAIUser('')).toBe(false);
|
|
19
|
+
expect(isAIUser('ai-assistant')).toBe(false); // case sensitive
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { generateInviteCode } from '../../src/utils/generateInviteCode.js';
|
|
3
|
+
|
|
4
|
+
describe('generateInviteCode', () => {
|
|
5
|
+
test('returns a string of length 5', () => {
|
|
6
|
+
const code = generateInviteCode();
|
|
7
|
+
expect(code).toHaveLength(5);
|
|
8
|
+
expect(typeof code).toBe('string');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('returns alphanumeric characters only', () => {
|
|
12
|
+
const code = generateInviteCode();
|
|
13
|
+
expect(code).toMatch(/^[a-z0-9]{5}$/);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('produces different codes on multiple calls', () => {
|
|
17
|
+
const codes = new Set<string>();
|
|
18
|
+
for (let i = 0; i < 50; i++) {
|
|
19
|
+
codes.add(generateInviteCode());
|
|
20
|
+
}
|
|
21
|
+
// With 50 calls, we expect at least some uniqueness (collision very unlikely)
|
|
22
|
+
expect(codes.size).toBeGreaterThan(1);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { logger } from '../../src/utils/logger.js';
|
|
3
|
+
|
|
4
|
+
describe('Logger', () => {
|
|
5
|
+
let consoleSpy: { log: ReturnType<typeof vi.spyOn>; warn: ReturnType<typeof vi.spyOn>; error: ReturnType<typeof vi.spyOn>; debug: ReturnType<typeof vi.spyOn> };
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
consoleSpy = {
|
|
9
|
+
log: vi.spyOn(console, 'log').mockImplementation(() => {}),
|
|
10
|
+
warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
|
|
11
|
+
error: vi.spyOn(console, 'error').mockImplementation(() => {}),
|
|
12
|
+
debug: vi.spyOn(console, 'debug').mockImplementation(() => {}),
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
logger.setMode('silent'); // reset for test env default
|
|
18
|
+
vi.restoreAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('logger has info, warn, error, debug methods', () => {
|
|
22
|
+
expect(typeof logger.info).toBe('function');
|
|
23
|
+
expect(typeof logger.warn).toBe('function');
|
|
24
|
+
expect(typeof logger.error).toBe('function');
|
|
25
|
+
expect(typeof logger.debug).toBe('function');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('in silent mode only error is logged', () => {
|
|
29
|
+
logger.setMode('silent');
|
|
30
|
+
logger.info('info');
|
|
31
|
+
logger.warn('warn');
|
|
32
|
+
logger.debug('debug');
|
|
33
|
+
logger.error('error');
|
|
34
|
+
|
|
35
|
+
expect(consoleSpy.log).not.toHaveBeenCalled();
|
|
36
|
+
expect(consoleSpy.warn).not.toHaveBeenCalled();
|
|
37
|
+
expect(consoleSpy.debug).not.toHaveBeenCalled();
|
|
38
|
+
expect(consoleSpy.error).toHaveBeenCalledWith(expect.stringContaining('error'));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('in normal mode info, warn, error are logged', () => {
|
|
42
|
+
logger.setMode('normal');
|
|
43
|
+
logger.info('info msg');
|
|
44
|
+
logger.warn('warn msg');
|
|
45
|
+
logger.error('error msg');
|
|
46
|
+
|
|
47
|
+
expect(consoleSpy.log).toHaveBeenCalledWith(expect.stringContaining('info msg'));
|
|
48
|
+
expect(consoleSpy.warn).toHaveBeenCalledWith(expect.stringContaining('warn msg'));
|
|
49
|
+
expect(consoleSpy.error).toHaveBeenCalledWith(expect.stringContaining('error msg'));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('in verbose mode debug is logged', () => {
|
|
53
|
+
logger.setMode('verbose');
|
|
54
|
+
logger.debug('debug msg');
|
|
55
|
+
expect(consoleSpy.debug).toHaveBeenCalledWith(expect.stringContaining('debug msg'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('formatted output includes level and message', () => {
|
|
59
|
+
logger.setMode('normal');
|
|
60
|
+
logger.info('test message');
|
|
61
|
+
const call = consoleSpy.log.mock.calls[0][0];
|
|
62
|
+
expect(call).toContain('INFO');
|
|
63
|
+
expect(call).toContain('test message');
|
|
64
|
+
expect(call).toMatch(/\[\d{4}-\d{2}-\d{2}T/); // ISO timestamp
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('context is included when provided', () => {
|
|
68
|
+
logger.setMode('normal');
|
|
69
|
+
logger.info('msg', { key: 'value' });
|
|
70
|
+
const call = consoleSpy.log.mock.calls[0][0];
|
|
71
|
+
expect(call).toContain('Context:');
|
|
72
|
+
expect(call).toContain('"key": "value"');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
handlePrismaError,
|
|
4
|
+
getFieldDisplayName,
|
|
5
|
+
} from '../../src/utils/prismaErrorHandler.js';
|
|
6
|
+
import {
|
|
7
|
+
PrismaClientKnownRequestError,
|
|
8
|
+
PrismaClientValidationError,
|
|
9
|
+
PrismaClientUnknownRequestError,
|
|
10
|
+
} from '@prisma/client/runtime/library';
|
|
11
|
+
|
|
12
|
+
describe('prismaErrorHandler', () => {
|
|
13
|
+
describe('handlePrismaError', () => {
|
|
14
|
+
test('handles PrismaClientKnownRequestError P2002 (unique constraint)', () => {
|
|
15
|
+
const error = new PrismaClientKnownRequestError('Unique constraint', {
|
|
16
|
+
code: 'P2002',
|
|
17
|
+
clientVersion: '6.0.0',
|
|
18
|
+
meta: { target: ['email'] },
|
|
19
|
+
});
|
|
20
|
+
const result = handlePrismaError(error);
|
|
21
|
+
expect(result.code).toBe('P2002');
|
|
22
|
+
expect(result.message).toContain('already exists');
|
|
23
|
+
expect(result.details).toContain('Unique constraint');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('handles P2002 with array target', () => {
|
|
27
|
+
const error = new PrismaClientKnownRequestError('Unique', {
|
|
28
|
+
code: 'P2002',
|
|
29
|
+
clientVersion: '6.0.0',
|
|
30
|
+
meta: { target: ['username', 'email'] },
|
|
31
|
+
});
|
|
32
|
+
const result = handlePrismaError(error);
|
|
33
|
+
expect(result.message).toContain('username, email');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('handles PrismaClientKnownRequestError P2025 (record not found)', () => {
|
|
37
|
+
const error = new PrismaClientKnownRequestError('Record not found', {
|
|
38
|
+
code: 'P2025',
|
|
39
|
+
clientVersion: '6.0.0',
|
|
40
|
+
});
|
|
41
|
+
const result = handlePrismaError(error);
|
|
42
|
+
expect(result.code).toBe('P2025');
|
|
43
|
+
expect(result.message).toContain('does not exist');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('handles PrismaClientKnownRequestError P2003 (foreign key)', () => {
|
|
47
|
+
const error = new PrismaClientKnownRequestError('Foreign key', {
|
|
48
|
+
code: 'P2003',
|
|
49
|
+
clientVersion: '6.0.0',
|
|
50
|
+
meta: { field_name: 'classId' },
|
|
51
|
+
});
|
|
52
|
+
const result = handlePrismaError(error);
|
|
53
|
+
expect(result.code).toBe('P2003');
|
|
54
|
+
expect(result.message).toContain('referenced by other records');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('handles PrismaClientValidationError', () => {
|
|
58
|
+
const error = new PrismaClientValidationError('Invalid data', {
|
|
59
|
+
clientVersion: '6.0.0',
|
|
60
|
+
});
|
|
61
|
+
const result = handlePrismaError(error);
|
|
62
|
+
expect(result.message).toBe('The data you provided is not valid');
|
|
63
|
+
expect(result.meta?.type).toBe('validation_error');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('handles PrismaClientUnknownRequestError', () => {
|
|
67
|
+
const error = new PrismaClientUnknownRequestError('Unknown DB error', {
|
|
68
|
+
clientVersion: '6.0.0',
|
|
69
|
+
});
|
|
70
|
+
const result = handlePrismaError(error);
|
|
71
|
+
expect(result.message).toBe('An unexpected database error occurred');
|
|
72
|
+
expect(result.meta?.type).toBe('unknown_request_error');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('handles generic Error', () => {
|
|
76
|
+
const error = new Error('Something went wrong');
|
|
77
|
+
const result = handlePrismaError(error);
|
|
78
|
+
expect(result.message).toBe('Something went wrong');
|
|
79
|
+
expect(result.details).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('handles non-Error unknown', () => {
|
|
83
|
+
const result = handlePrismaError('string error');
|
|
84
|
+
expect(result.message).toBe('An unknown database error occurred');
|
|
85
|
+
expect(result.details).toBe('string error');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('getFieldDisplayName', () => {
|
|
90
|
+
test('returns mapped display names for known fields', () => {
|
|
91
|
+
expect(getFieldDisplayName('username')).toBe('username');
|
|
92
|
+
expect(getFieldDisplayName('email')).toBe('email address');
|
|
93
|
+
expect(getFieldDisplayName('classId')).toBe('class');
|
|
94
|
+
expect(getFieldDisplayName('assignmentId')).toBe('assignment');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('returns field name as-is for unknown fields', () => {
|
|
98
|
+
expect(getFieldDisplayName('unknownField')).toBe('unknownField');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
withPrismaErrorHandling,
|
|
4
|
+
prismaWrapper,
|
|
5
|
+
} from '../../src/utils/prismaWrapper.js';
|
|
6
|
+
import { TRPCError } from '@trpc/server';
|
|
7
|
+
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
|
|
8
|
+
|
|
9
|
+
// Suppress console.error in tests
|
|
10
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
11
|
+
|
|
12
|
+
describe('prismaWrapper', () => {
|
|
13
|
+
describe('withPrismaErrorHandling', () => {
|
|
14
|
+
test('returns result when operation succeeds', async () => {
|
|
15
|
+
const result = await withPrismaErrorHandling(
|
|
16
|
+
async () => ({ id: '1', name: 'test' }),
|
|
17
|
+
'test op'
|
|
18
|
+
);
|
|
19
|
+
expect(result).toEqual({ id: '1', name: 'test' });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('throws TRPCError with user-friendly message on Prisma P2002', async () => {
|
|
23
|
+
const error = new PrismaClientKnownRequestError('Unique', {
|
|
24
|
+
code: 'P2002',
|
|
25
|
+
clientVersion: '6.0.0',
|
|
26
|
+
meta: { target: ['email'] },
|
|
27
|
+
});
|
|
28
|
+
await expect(
|
|
29
|
+
withPrismaErrorHandling(async () => {
|
|
30
|
+
throw error;
|
|
31
|
+
}, 'create user')
|
|
32
|
+
).rejects.toThrow(TRPCError);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await withPrismaErrorHandling(async () => {
|
|
36
|
+
throw error;
|
|
37
|
+
}, 'create user');
|
|
38
|
+
} catch (e) {
|
|
39
|
+
expect(e).toBeInstanceOf(TRPCError);
|
|
40
|
+
expect((e as TRPCError).code).toBe('CONFLICT');
|
|
41
|
+
expect((e as TRPCError).message).toContain('already exists');
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('throws TRPCError with NOT_FOUND on P2025', async () => {
|
|
46
|
+
const error = new PrismaClientKnownRequestError('Not found', {
|
|
47
|
+
code: 'P2025',
|
|
48
|
+
clientVersion: '6.0.0',
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
await withPrismaErrorHandling(async () => {
|
|
52
|
+
throw error;
|
|
53
|
+
});
|
|
54
|
+
} catch (e) {
|
|
55
|
+
expect(e).toBeInstanceOf(TRPCError);
|
|
56
|
+
expect((e as TRPCError).code).toBe('NOT_FOUND');
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('prismaWrapper helpers', () => {
|
|
62
|
+
test('findUnique returns result', async () => {
|
|
63
|
+
const result = await prismaWrapper.findUnique(
|
|
64
|
+
async () => ({ id: '1' }),
|
|
65
|
+
'test'
|
|
66
|
+
);
|
|
67
|
+
expect(result).toEqual({ id: '1' });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('create propagates Prisma errors as TRPCError', async () => {
|
|
71
|
+
const error = new PrismaClientKnownRequestError('Unique', {
|
|
72
|
+
code: 'P2002',
|
|
73
|
+
clientVersion: '6.0.0',
|
|
74
|
+
});
|
|
75
|
+
await expect(
|
|
76
|
+
prismaWrapper.create(async () => {
|
|
77
|
+
throw error;
|
|
78
|
+
})
|
|
79
|
+
).rejects.toThrow(TRPCError);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { test, expect, describe, beforeAll } from 'vitest';
|
|
2
|
+
import { user1Caller } from './setup';
|
|
3
|
+
|
|
4
|
+
describe('Worksheet Router', () => {
|
|
5
|
+
let testClass: any;
|
|
6
|
+
let worksheetId: string;
|
|
7
|
+
let questionId: string;
|
|
8
|
+
let secondQuestionId: string;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
testClass = await user1Caller.class.create({
|
|
12
|
+
name: 'Worksheet Test Class',
|
|
13
|
+
subject: 'Physics',
|
|
14
|
+
section: '12th Grade',
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('create', () => {
|
|
19
|
+
test('should create a worksheet', async () => {
|
|
20
|
+
const worksheet = await user1Caller.worksheet.create({
|
|
21
|
+
classId: testClass.id,
|
|
22
|
+
name: 'Chapter 1 Quiz',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(worksheet).toBeDefined();
|
|
26
|
+
expect(worksheet.name).toBe('Chapter 1 Quiz');
|
|
27
|
+
expect(worksheet.classId).toBe(testClass.id);
|
|
28
|
+
worksheetId = worksheet.id;
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('listWorksheets', () => {
|
|
33
|
+
test('should list worksheets for a class', async () => {
|
|
34
|
+
const worksheets = await user1Caller.worksheet.listWorksheets({
|
|
35
|
+
classId: testClass.id,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(Array.isArray(worksheets)).toBe(true);
|
|
39
|
+
expect(worksheets.length).toBeGreaterThanOrEqual(1);
|
|
40
|
+
expect(worksheets.some((w) => w.id === worksheetId)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('getWorksheet', () => {
|
|
45
|
+
test('should get a worksheet by ID', async () => {
|
|
46
|
+
const worksheet = await user1Caller.worksheet.getWorksheet({
|
|
47
|
+
worksheetId,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(worksheet).toBeDefined();
|
|
51
|
+
expect(worksheet.id).toBe(worksheetId);
|
|
52
|
+
expect(worksheet.name).toBe('Chapter 1 Quiz');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('should fail for non-existent worksheet', async () => {
|
|
56
|
+
await expect(
|
|
57
|
+
user1Caller.worksheet.getWorksheet({ worksheetId: 'nonexistent' }),
|
|
58
|
+
).rejects.toThrow();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('updateWorksheet', () => {
|
|
63
|
+
test('should update worksheet name', async () => {
|
|
64
|
+
const updated = await user1Caller.worksheet.updateWorksheet({
|
|
65
|
+
worksheetId,
|
|
66
|
+
name: 'Chapter 1 Test',
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(updated.name).toBe('Chapter 1 Test');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('addQuestion', () => {
|
|
74
|
+
test('should add a short answer question', async () => {
|
|
75
|
+
const question = await user1Caller.worksheet.addQuestion({
|
|
76
|
+
worksheetId,
|
|
77
|
+
question: 'What is Newton\'s second law?',
|
|
78
|
+
answer: 'F = ma',
|
|
79
|
+
points: 5,
|
|
80
|
+
type: 'SHORT_ANSWER',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(question).toBeDefined();
|
|
84
|
+
expect(question.question).toBe('What is Newton\'s second law?');
|
|
85
|
+
expect(question.answer).toBe('F = ma');
|
|
86
|
+
expect(question.points).toBe(5);
|
|
87
|
+
questionId = question.id;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('should add a multiple choice question', async () => {
|
|
91
|
+
const question = await user1Caller.worksheet.addQuestion({
|
|
92
|
+
worksheetId,
|
|
93
|
+
question: 'What is the SI unit of force?',
|
|
94
|
+
answer: 'Newton',
|
|
95
|
+
points: 3,
|
|
96
|
+
type: 'MULTIPLE_CHOICE',
|
|
97
|
+
options: [
|
|
98
|
+
{ id: 'a', text: 'Joule', isCorrect: false },
|
|
99
|
+
{ id: 'b', text: 'Newton', isCorrect: true },
|
|
100
|
+
{ id: 'c', text: 'Watt', isCorrect: false },
|
|
101
|
+
{ id: 'd', text: 'Pascal', isCorrect: false },
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(question).toBeDefined();
|
|
106
|
+
expect(question.type).toBe('MULTIPLE_CHOICE');
|
|
107
|
+
secondQuestionId = question.id;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('should fail for non-existent worksheet', async () => {
|
|
111
|
+
await expect(
|
|
112
|
+
user1Caller.worksheet.addQuestion({
|
|
113
|
+
worksheetId: 'nonexistent',
|
|
114
|
+
question: 'Test',
|
|
115
|
+
answer: 'Test',
|
|
116
|
+
type: 'SHORT_ANSWER',
|
|
117
|
+
}),
|
|
118
|
+
).rejects.toThrow();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('updateQuestion', () => {
|
|
123
|
+
test('should update a question', async () => {
|
|
124
|
+
const updated = await user1Caller.worksheet.updateQuestion({
|
|
125
|
+
worksheetId,
|
|
126
|
+
questionId,
|
|
127
|
+
question: 'State Newton\'s second law of motion.',
|
|
128
|
+
points: 10,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(updated.question).toBe('State Newton\'s second law of motion.');
|
|
132
|
+
expect(updated.points).toBe(10);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('reorderQuestions', () => {
|
|
137
|
+
test('should reorder questions', async () => {
|
|
138
|
+
const result = await user1Caller.worksheet.reorderQuestions({
|
|
139
|
+
worksheetId,
|
|
140
|
+
movedId: secondQuestionId,
|
|
141
|
+
position: 'before',
|
|
142
|
+
targetId: questionId,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(Array.isArray(result)).toBe(true);
|
|
146
|
+
expect(result.length).toBe(2);
|
|
147
|
+
const ids = result.map((r) => r.id);
|
|
148
|
+
expect(ids).toContain(questionId);
|
|
149
|
+
expect(ids).toContain(secondQuestionId);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('deleteQuestion', () => {
|
|
154
|
+
test('should delete a question', async () => {
|
|
155
|
+
const result = await user1Caller.worksheet.deleteQuestion({
|
|
156
|
+
worksheetId,
|
|
157
|
+
questionId: secondQuestionId,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(result.id).toBe(secondQuestionId);
|
|
161
|
+
|
|
162
|
+
const worksheet = await user1Caller.worksheet.getWorksheet({ worksheetId });
|
|
163
|
+
expect(worksheet.questions.length).toBe(1);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('deleteWorksheet', () => {
|
|
168
|
+
test('should delete a worksheet', async () => {
|
|
169
|
+
const toDelete = await user1Caller.worksheet.create({
|
|
170
|
+
classId: testClass.id,
|
|
171
|
+
name: 'Temporary Worksheet',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const result = await user1Caller.worksheet.deleteWorksheet({
|
|
175
|
+
worksheetId: toDelete.id,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(result.id).toBe(toDelete.id);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
"skipLibCheck": true,
|
|
10
10
|
"forceConsistentCasingInFileNames": true,
|
|
11
11
|
"outDir": "dist",
|
|
12
|
-
"baseUrl": ".",
|
|
12
|
+
"baseUrl": ".",
|
|
13
13
|
"declaration": true,
|
|
14
|
-
"declarationMap": true
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"inlineSources": true,
|
|
17
|
+
|
|
18
|
+
// Set `sourceRoot` to "/" to strip the build path prefix
|
|
19
|
+
// from generated source code references.
|
|
20
|
+
// This improves issue grouping in Sentry.
|
|
21
|
+
"sourceRoot": "/"
|
|
15
22
|
},
|
|
16
23
|
"include": ["src/**/*"],
|
|
17
24
|
"exclude": ["node_modules", "dist"]
|
package/vitest.config.ts
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import { config } from 'dotenv';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
// Load test environment variables before anything else
|
|
6
|
+
config({ path: resolve(process.cwd(), '.env.test'), override: true });
|
|
2
7
|
|
|
3
8
|
export default defineConfig({
|
|
4
9
|
test: {
|
|
5
10
|
globals: true,
|
|
6
11
|
environment: 'node',
|
|
7
12
|
hookTimeout: 60000, // 60 seconds
|
|
13
|
+
testTimeout: 30000, // 30 seconds per test
|
|
8
14
|
include: ['tests/**/*.test.ts'],
|
|
9
|
-
|
|
15
|
+
globalSetup: ['./tests/globalSetup.ts'],
|
|
16
|
+
setupFiles: ['./tests/setup.ts'],
|
|
17
|
+
sequence: {
|
|
18
|
+
concurrent: false,
|
|
19
|
+
},
|
|
20
|
+
coverage: {
|
|
21
|
+
exclude: [
|
|
22
|
+
"src/types/trpc.ts",
|
|
23
|
+
"src/instrument.ts",
|
|
24
|
+
"*.config.ts",
|
|
25
|
+
"tests/**",
|
|
26
|
+
"generated/**",
|
|
27
|
+
"prisma/**",
|
|
28
|
+
"scripts/**",
|
|
29
|
+
"src/lib/prisma.ts",
|
|
30
|
+
"src/seedDatabase.ts",
|
|
31
|
+
"src/socket/**",
|
|
32
|
+
"**/node_modules/**",
|
|
33
|
+
"**/dist/**",
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
env: {
|
|
37
|
+
NODE_ENV: 'test',
|
|
38
|
+
},
|
|
10
39
|
},
|
|
11
40
|
});
|