@studious-lms/server 1.1.26 → 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 +15 -5
- package/dist/lib/fileUpload.js.map +1 -0
- package/dist/lib/googleCloudStorage.d.ts +6 -0
- package/dist/lib/googleCloudStorage.d.ts.map +1 -1
- package/dist/lib/googleCloudStorage.js +26 -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 +2 -2
- package/dist/lib/notificationHandler.d.ts.map +1 -1
- package/dist/lib/notificationHandler.js +4 -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 +6438 -3910
- 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 +543 -77
- package/dist/routers/announcement.js.map +1 -0
- package/dist/routers/assignment.d.ts +419 -357
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +100 -1689
- 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 -298
- 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 -885
- 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 +23 -8
- package/dist/routers/section.d.ts.map +1 -1
- package/dist/routers/section.js +23 -273
- 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 +309 -288
- 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 +3 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +8 -1
- 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 +13 -6
- package/src/lib/googleCloudStorage.ts +23 -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 +616 -79
- package/src/routers/assignment.ts +148 -1827
- 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 -344
- package/src/routers/folder.ts +107 -836
- package/src/routers/labChat.ts +29 -969
- 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 -322
- package/src/routers/user.ts +49 -226
- package/src/routers/worksheet.ts +252 -0
- package/src/seedDatabase.ts +328 -289
- 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 +4 -1
- 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 -59
- 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/src/lib/notificationHandler.ts +0 -36
- package/tests/auth.test.ts +0 -25
|
@@ -1,32 +1,46 @@
|
|
|
1
|
-
import { test, expect, describe, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
1
|
+
import { test, expect, describe, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { expectForbidden, expectRejects, expectUnauth } from '../helpers';
|
|
3
|
+
import { prisma } from '../../src/lib/prisma';
|
|
4
|
+
import { caller, user1Caller, user2Caller, user3Caller } from '../setup';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../src/lib/googleCloudStorage.js', async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal<typeof import('../../src/lib/googleCloudStorage.js')>();
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
copyFile: vi.fn().mockResolvedValue(undefined),
|
|
11
|
+
};
|
|
12
|
+
});
|
|
6
13
|
|
|
7
14
|
describe('Class Router', () => {
|
|
8
15
|
let testClass: any;
|
|
9
16
|
let testClass2: any;
|
|
17
|
+
const createdClassIds: string[] = [];
|
|
10
18
|
|
|
11
19
|
beforeEach(async () => {
|
|
12
|
-
// Create test classes for each test
|
|
20
|
+
// Create test classes for each test with unique names to avoid conflicts
|
|
21
|
+
const timestamp = Date.now();
|
|
13
22
|
testClass = await user1Caller.class.create({
|
|
14
|
-
name:
|
|
23
|
+
name: `Test Class 1`,
|
|
15
24
|
subject: 'Mathematics',
|
|
16
25
|
section: '10th Grade',
|
|
17
26
|
});
|
|
18
27
|
|
|
19
28
|
testClass2 = await user2Caller.class.create({
|
|
20
|
-
name:
|
|
29
|
+
name: `Test Class 2`,
|
|
21
30
|
subject: 'Science',
|
|
22
31
|
section: '11th Grade',
|
|
23
32
|
});
|
|
33
|
+
|
|
34
|
+
// Track created classes for cleanup
|
|
35
|
+
if (testClass?.id) createdClassIds.push(testClass.id);
|
|
36
|
+
if (testClass2?.id) createdClassIds.push(testClass2.id);
|
|
24
37
|
});
|
|
25
38
|
|
|
26
39
|
describe('create', () => {
|
|
27
40
|
test('should create a class successfully', async () => {
|
|
28
41
|
expect(testClass).toBeDefined();
|
|
29
|
-
expect(testClass.
|
|
42
|
+
expect(testClass.id).toBeDefined();
|
|
43
|
+
expect(testClass.name).toContain('Test Class 1');
|
|
30
44
|
expect(testClass.subject).toBe('Mathematics');
|
|
31
45
|
expect(testClass.section).toBe('10th Grade');
|
|
32
46
|
});
|
|
@@ -37,27 +51,19 @@ describe('Class Router', () => {
|
|
|
37
51
|
expect(testClass.id).not.toBe(testClass2.id);
|
|
38
52
|
});
|
|
39
53
|
|
|
40
|
-
test('should fail to create class without authentication',
|
|
41
|
-
|
|
42
|
-
req: { headers: {} } as any,
|
|
43
|
-
res: {} as any,
|
|
44
|
-
});
|
|
45
|
-
const router = appRouter.createCaller(invalidCaller);
|
|
46
|
-
|
|
47
|
-
await expect(router.class.create({
|
|
54
|
+
test('should fail to create class without authentication', () =>
|
|
55
|
+
expectUnauth((c) => c.class.create({
|
|
48
56
|
name: 'Test Class',
|
|
49
57
|
subject: 'Mathematics',
|
|
50
58
|
section: '10th Grade',
|
|
51
|
-
}))
|
|
52
|
-
});
|
|
59
|
+
})));
|
|
53
60
|
|
|
54
|
-
test('should fail to create class with missing required fields',
|
|
55
|
-
|
|
61
|
+
test('should fail to create class with missing required fields', () =>
|
|
62
|
+
// @ts-expect-error - test case
|
|
63
|
+
expectRejects(() => user1Caller.class.create({
|
|
56
64
|
name: '',
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})).rejects.toThrow();
|
|
60
|
-
});
|
|
65
|
+
color: '#3B82F6',
|
|
66
|
+
})));
|
|
61
67
|
});
|
|
62
68
|
|
|
63
69
|
describe('getAll', () => {
|
|
@@ -72,19 +78,13 @@ describe('Class Router', () => {
|
|
|
72
78
|
|
|
73
79
|
test('should include user\'s own classes as teacher', async () => {
|
|
74
80
|
const classes = await user1Caller.class.getAll();
|
|
75
|
-
const userClass = classes.teacherInClass.find((c: any) => c.
|
|
81
|
+
const userClass = classes.teacherInClass.find((c: any) => c.id === testClass.id);
|
|
76
82
|
expect(userClass).toBeDefined();
|
|
83
|
+
expect(userClass?.id).toBe(testClass.id);
|
|
77
84
|
});
|
|
78
85
|
|
|
79
|
-
test('should fail to get classes without authentication',
|
|
80
|
-
|
|
81
|
-
req: { headers: {} } as any,
|
|
82
|
-
res: {} as any,
|
|
83
|
-
});
|
|
84
|
-
const router = appRouter.createCaller(invalidCaller);
|
|
85
|
-
|
|
86
|
-
await expect(router.class.getAll()).rejects.toThrow();
|
|
87
|
-
});
|
|
86
|
+
test('should fail to get classes without authentication', () =>
|
|
87
|
+
expectUnauth((c) => c.class.getAll()));
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
describe('get', () => {
|
|
@@ -96,19 +96,11 @@ describe('Class Router', () => {
|
|
|
96
96
|
expect(classData.class.name).toBe('Test Class 1');
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
test('should fail to get non-existent class',
|
|
100
|
-
|
|
101
|
-
});
|
|
99
|
+
test('should fail to get non-existent class', () =>
|
|
100
|
+
expectRejects(() => user1Caller.class.get({ classId: 'non-existent-id' })));
|
|
102
101
|
|
|
103
|
-
test('should fail to get class without authentication',
|
|
104
|
-
|
|
105
|
-
req: { headers: {} } as any,
|
|
106
|
-
res: {} as any,
|
|
107
|
-
});
|
|
108
|
-
const router = appRouter.createCaller(invalidCaller);
|
|
109
|
-
|
|
110
|
-
await expect(router.class.get({ classId: testClass.id })).rejects.toThrow();
|
|
111
|
-
});
|
|
102
|
+
test('should fail to get class without authentication', () =>
|
|
103
|
+
expectUnauth((c) => c.class.get({ classId: testClass.id })));
|
|
112
104
|
});
|
|
113
105
|
|
|
114
106
|
describe('update', () => {
|
|
@@ -127,23 +119,21 @@ describe('Class Router', () => {
|
|
|
127
119
|
expect(updatedClass.updatedClass.section).toBe('12th Grade');
|
|
128
120
|
});
|
|
129
121
|
|
|
130
|
-
test('should fail to update class user is not teacher of',
|
|
131
|
-
|
|
122
|
+
test('should fail to update class user is not teacher of', () =>
|
|
123
|
+
expectForbidden(() => user2Caller.class.update({
|
|
132
124
|
classId: testClass.id,
|
|
133
125
|
name: 'Updated Test Class',
|
|
134
126
|
subject: 'Physics',
|
|
135
127
|
section: '12th Grade',
|
|
136
|
-
}))
|
|
137
|
-
});
|
|
128
|
+
})));
|
|
138
129
|
|
|
139
|
-
test('should fail to update non-existent class',
|
|
140
|
-
|
|
130
|
+
test('should fail to update non-existent class', () =>
|
|
131
|
+
expectRejects(() => user1Caller.class.update({
|
|
141
132
|
classId: 'non-existent-id',
|
|
142
133
|
name: 'Updated Test Class',
|
|
143
134
|
subject: 'Physics',
|
|
144
135
|
section: '12th Grade',
|
|
145
|
-
}))
|
|
146
|
-
});
|
|
136
|
+
})));
|
|
147
137
|
});
|
|
148
138
|
|
|
149
139
|
describe('delete', () => {
|
|
@@ -152,15 +142,19 @@ describe('Class Router', () => {
|
|
|
152
142
|
expect(result).toBeDefined();
|
|
153
143
|
expect(result.deletedClass).toBeDefined();
|
|
154
144
|
expect(result.deletedClass.id).toBe(testClass.id);
|
|
145
|
+
|
|
146
|
+
// Remove from cleanup list since it's already deleted
|
|
147
|
+
const index = createdClassIds.indexOf(testClass.id);
|
|
148
|
+
if (index > -1) {
|
|
149
|
+
createdClassIds.splice(index, 1);
|
|
150
|
+
}
|
|
155
151
|
});
|
|
156
152
|
|
|
157
|
-
test('should fail to delete class user is not teacher of',
|
|
158
|
-
|
|
159
|
-
});
|
|
153
|
+
test('should fail to delete class user is not teacher of', () =>
|
|
154
|
+
expectForbidden(() => user2Caller.class.delete({ classId: testClass.id, id: testClass.id })));
|
|
160
155
|
|
|
161
|
-
test('should fail to delete non-existent class',
|
|
162
|
-
|
|
163
|
-
});
|
|
156
|
+
test('should fail to delete non-existent class', () =>
|
|
157
|
+
expectRejects(() => user1Caller.class.delete({ classId: 'non-existent-id', id: 'non-existent-id' })));
|
|
164
158
|
});
|
|
165
159
|
|
|
166
160
|
describe('addStudent', () => {
|
|
@@ -175,13 +169,14 @@ describe('Class Router', () => {
|
|
|
175
169
|
expect(result.newStudent).toBeDefined();
|
|
176
170
|
});
|
|
177
171
|
|
|
178
|
-
test('should fail to add student if user is not class teacher',
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
172
|
+
test('should fail to add student if user is not class teacher', () =>
|
|
173
|
+
expectForbidden(async () => {
|
|
174
|
+
const user1Profile = await user1Caller.user.getProfile();
|
|
175
|
+
return user2Caller.class.addStudent({
|
|
176
|
+
classId: testClass.id,
|
|
177
|
+
studentId: user1Profile.id,
|
|
178
|
+
});
|
|
179
|
+
}));
|
|
185
180
|
});
|
|
186
181
|
|
|
187
182
|
describe('changeRole', () => {
|
|
@@ -198,24 +193,27 @@ describe('Class Router', () => {
|
|
|
198
193
|
expect(result.user.type).toBe('teacher');
|
|
199
194
|
});
|
|
200
195
|
|
|
201
|
-
test('should fail to change role if user is not class teacher',
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
196
|
+
test('should fail to change role if user is not class teacher', () =>
|
|
197
|
+
expectForbidden(async () => {
|
|
198
|
+
const user1Profile = await user1Caller.user.getProfile();
|
|
199
|
+
return user2Caller.class.changeRole({
|
|
200
|
+
classId: testClass.id,
|
|
201
|
+
userId: user1Profile.id,
|
|
202
|
+
type: 'teacher',
|
|
203
|
+
});
|
|
204
|
+
}));
|
|
209
205
|
});
|
|
210
206
|
|
|
211
207
|
describe('removeMember', () => {
|
|
212
208
|
test('should remove member successfully', async () => {
|
|
213
209
|
// First add a student
|
|
214
210
|
const user2Profile = await user2Caller.user.getProfile();
|
|
215
|
-
await user1Caller.class.addStudent({
|
|
211
|
+
const addResult = await user1Caller.class.addStudent({
|
|
216
212
|
classId: testClass.id,
|
|
217
213
|
studentId: user2Profile.id,
|
|
218
214
|
});
|
|
215
|
+
expect(addResult).toBeDefined();
|
|
216
|
+
expect(addResult.newStudent).toBeDefined();
|
|
219
217
|
|
|
220
218
|
// Then remove them
|
|
221
219
|
const result = await user1Caller.class.removeMember({
|
|
@@ -227,30 +225,38 @@ describe('Class Router', () => {
|
|
|
227
225
|
expect(result.removedUserId).toBe(user2Profile.id);
|
|
228
226
|
});
|
|
229
227
|
|
|
230
|
-
test('should fail to remove member if user is not class teacher',
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
228
|
+
test('should fail to remove member if user is not class teacher', () =>
|
|
229
|
+
expectForbidden(async () => {
|
|
230
|
+
const user1Profile = await user1Caller.user.getProfile();
|
|
231
|
+
return user2Caller.class.removeMember({
|
|
232
|
+
classId: testClass.id,
|
|
233
|
+
userId: user1Profile.id,
|
|
234
|
+
});
|
|
235
|
+
}));
|
|
237
236
|
});
|
|
238
237
|
|
|
239
238
|
describe('join', () => {
|
|
240
239
|
test('should join class with class code successfully', async () => {
|
|
241
240
|
// First create an invite code
|
|
242
241
|
const inviteCode = await user1Caller.class.createInviteCode({ classId: testClass.id });
|
|
242
|
+
expect(inviteCode).toBeDefined();
|
|
243
|
+
expect(inviteCode.code).toBeDefined();
|
|
243
244
|
|
|
244
245
|
// Then join with the code
|
|
245
246
|
const result = await user2Caller.class.join({ classCode: inviteCode.code });
|
|
246
247
|
expect(result).toBeDefined();
|
|
247
248
|
expect(result.joinedClass).toBeDefined();
|
|
248
249
|
expect(result.joinedClass.id).toBe(testClass.id);
|
|
250
|
+
|
|
251
|
+
// Clean up: remove student from class
|
|
252
|
+
await user1Caller.class.removeMember({
|
|
253
|
+
classId: testClass.id,
|
|
254
|
+
userId: (await user2Caller.user.getProfile()).id,
|
|
255
|
+
});
|
|
249
256
|
});
|
|
250
257
|
|
|
251
|
-
test('should fail to join with invalid class code',
|
|
252
|
-
|
|
253
|
-
});
|
|
258
|
+
test('should fail to join with invalid class code', () =>
|
|
259
|
+
expectRejects(() => user2Caller.class.join({ classCode: 'invalid-code' })));
|
|
254
260
|
});
|
|
255
261
|
|
|
256
262
|
describe('getInviteCode', () => {
|
|
@@ -261,9 +267,8 @@ describe('Class Router', () => {
|
|
|
261
267
|
expect(typeof result.code).toBe('string');
|
|
262
268
|
});
|
|
263
269
|
|
|
264
|
-
test('should fail to get invite code if user is not class teacher',
|
|
265
|
-
|
|
266
|
-
});
|
|
270
|
+
test('should fail to get invite code if user is not class teacher', () =>
|
|
271
|
+
expectForbidden(() => user2Caller.class.getInviteCode({ classId: testClass.id })));
|
|
267
272
|
});
|
|
268
273
|
|
|
269
274
|
describe('createInviteCode', () => {
|
|
@@ -274,8 +279,74 @@ describe('Class Router', () => {
|
|
|
274
279
|
expect(typeof result.code).toBe('string');
|
|
275
280
|
});
|
|
276
281
|
|
|
277
|
-
test('should fail to create invite code if user is not class teacher',
|
|
278
|
-
|
|
282
|
+
test('should fail to create invite code if user is not class teacher', () =>
|
|
283
|
+
expectForbidden(() => user2Caller.class.createInviteCode({ classId: testClass.id })));
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('exportClass', () => {
|
|
287
|
+
test('teacher can export class', async () => {
|
|
288
|
+
// Ensure class has classFiles (root folder) by fetching it
|
|
289
|
+
await user1Caller.folder.getRootFolder({ classId: testClass.id });
|
|
290
|
+
|
|
291
|
+
const exported = await user1Caller.class.exportClass({ classId: testClass.id });
|
|
292
|
+
|
|
293
|
+
expect(exported).toBeDefined();
|
|
294
|
+
expect(exported.id).toBe(testClass.id);
|
|
295
|
+
expect(exported.name).toBe('Test Class 1');
|
|
296
|
+
expect(exported.subject).toBe('Mathematics');
|
|
297
|
+
expect(exported.assignments).toBeDefined();
|
|
298
|
+
expect(Array.isArray(exported.assignments)).toBe(true);
|
|
299
|
+
expect(exported.classFiles).toBeDefined();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('non-teacher cannot export class', () =>
|
|
303
|
+
expectForbidden(() => user2Caller.class.exportClass({ classId: testClass.id })));
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('importClass', () => {
|
|
307
|
+
test('teacher can import class with folder structure', async () => {
|
|
308
|
+
await user1Caller.folder.getRootFolder({ classId: testClass.id });
|
|
309
|
+
await user1Caller.folder.create({
|
|
310
|
+
classId: testClass.id,
|
|
311
|
+
name: 'Source Folder',
|
|
312
|
+
color: '#3B82F6',
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const exported = await user1Caller.class.exportClass({ classId: testClass.id });
|
|
316
|
+
const newClass = await user1Caller.class.create({
|
|
317
|
+
name: 'Imported Class',
|
|
318
|
+
subject: 'Mathematics',
|
|
319
|
+
section: '10th Grade',
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const importedId = await user1Caller.class.importClass({
|
|
323
|
+
classId: newClass.id,
|
|
324
|
+
year: new Date().getFullYear(),
|
|
325
|
+
classData: exported,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const importedClass = await user1Caller.class.get({ classId: importedId });
|
|
329
|
+
expect(importedClass.class.name).toBe(exported.name);
|
|
330
|
+
expect(importedClass.class.subject).toBe(exported.subject);
|
|
331
|
+
|
|
332
|
+
const rootFolder = await user1Caller.folder.getRootFolder({ classId: importedId });
|
|
333
|
+
expect(rootFolder).toBeDefined();
|
|
334
|
+
expect(rootFolder.name).toBe('Class Files');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
test('non-teacher cannot import class', async () => {
|
|
339
|
+
await user1Caller.folder.getRootFolder({ classId: testClass.id });
|
|
340
|
+
const exported = await user1Caller.class.exportClass({ classId: testClass.id });
|
|
341
|
+
const newClassId = `import-forbidden-${Date.now()}`;
|
|
342
|
+
|
|
343
|
+
await expect(
|
|
344
|
+
user3Caller.class.importClass({
|
|
345
|
+
classId: newClassId,
|
|
346
|
+
year: new Date().getFullYear(),
|
|
347
|
+
classData: exported,
|
|
348
|
+
})
|
|
349
|
+
).rejects.toThrow();
|
|
279
350
|
});
|
|
280
351
|
});
|
|
281
352
|
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { test, expect, describe, beforeAll } from 'vitest';
|
|
2
|
+
import { user1Caller, user2Caller } from '../setup';
|
|
3
|
+
|
|
4
|
+
describe('Comment Router', () => {
|
|
5
|
+
let testClass: any;
|
|
6
|
+
let announcement: any;
|
|
7
|
+
let commentId: string;
|
|
8
|
+
let replyId: string;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
testClass = await user1Caller.class.create({
|
|
12
|
+
name: 'Comment Test Class',
|
|
13
|
+
subject: 'History',
|
|
14
|
+
section: '11th Grade',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const result = await user1Caller.announcement.create({
|
|
18
|
+
classId: testClass.id,
|
|
19
|
+
remarks: 'Test announcement for comments',
|
|
20
|
+
});
|
|
21
|
+
announcement = result.announcement;
|
|
22
|
+
|
|
23
|
+
const comment = await user1Caller.announcement.addComment({
|
|
24
|
+
announcementId: announcement.id,
|
|
25
|
+
classId: testClass.id,
|
|
26
|
+
content: 'Test comment on announcement',
|
|
27
|
+
});
|
|
28
|
+
commentId = comment.comment.id;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('get', () => {
|
|
32
|
+
test('should get a comment by ID', async () => {
|
|
33
|
+
const comment = await user1Caller.comment.get({ id: commentId });
|
|
34
|
+
|
|
35
|
+
expect(comment).toBeDefined();
|
|
36
|
+
expect(comment.id).toBe(commentId);
|
|
37
|
+
expect(comment.content).toBe('Test comment on announcement');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should fail for non-existent comment', async () => {
|
|
41
|
+
await expect(
|
|
42
|
+
user1Caller.comment.get({ id: 'nonexistent-id' }),
|
|
43
|
+
).rejects.toThrow();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('replyToComment', () => {
|
|
48
|
+
test('should reply to a comment', async () => {
|
|
49
|
+
const reply = await user1Caller.comment.replyToComment({
|
|
50
|
+
parentCommentId: commentId,
|
|
51
|
+
content: 'This is a reply',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(reply).toBeDefined();
|
|
55
|
+
expect(reply.content).toBe('This is a reply');
|
|
56
|
+
expect(reply.parentCommentId).toBe(commentId);
|
|
57
|
+
replyId = reply.id;
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('getReplies', () => {
|
|
62
|
+
test('should get replies to a comment', async () => {
|
|
63
|
+
const replies = await user1Caller.comment.getReplies({
|
|
64
|
+
commentId,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(Array.isArray(replies)).toBe(true);
|
|
68
|
+
expect(replies.length).toBeGreaterThanOrEqual(1);
|
|
69
|
+
expect(replies.some((r) => r.id === replyId)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('should return empty array for comment with no replies', async () => {
|
|
73
|
+
const replies = await user1Caller.comment.getReplies({
|
|
74
|
+
commentId: replyId,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(replies).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('reactions', () => {
|
|
82
|
+
test('should add a reaction to a comment', async () => {
|
|
83
|
+
const result = await user1Caller.comment.addReaction({
|
|
84
|
+
id: commentId,
|
|
85
|
+
type: 'HEART',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(result.reaction).toBeDefined();
|
|
89
|
+
expect(result.reaction.type).toBe('HEART');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should get reactions for a comment', async () => {
|
|
93
|
+
const result = await user1Caller.comment.getReactions({
|
|
94
|
+
commentId,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(result).toBeDefined();
|
|
98
|
+
expect(result.counts.HEART).toBe(1);
|
|
99
|
+
expect(result.total).toBe(1);
|
|
100
|
+
expect(result.userReaction).toBe('HEART');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('should update reaction type', async () => {
|
|
104
|
+
const result = await user1Caller.comment.addReaction({
|
|
105
|
+
id: commentId,
|
|
106
|
+
type: 'THUMBSUP',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(result.reaction.type).toBe('THUMBSUP');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('should remove a reaction', async () => {
|
|
113
|
+
const result = await user1Caller.comment.removeReaction({
|
|
114
|
+
commentId,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(result.success).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('should fail to remove non-existent reaction', async () => {
|
|
121
|
+
await expect(
|
|
122
|
+
user1Caller.comment.removeReaction({ commentId }),
|
|
123
|
+
).rejects.toThrow();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { test, expect, describe, beforeAll } from 'vitest';
|
|
2
|
+
import { user1Caller, user2Caller, user3Caller } from '../setup';
|
|
3
|
+
import { expectUnauth } from '../helpers';
|
|
4
|
+
|
|
5
|
+
describe('Conversation Router', () => {
|
|
6
|
+
let dmConversation: any;
|
|
7
|
+
let groupConversation: any;
|
|
8
|
+
let user3Id: string;
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
const profile = await user3Caller.user.getProfile();
|
|
12
|
+
user3Id = profile.id;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('create', () => {
|
|
16
|
+
test('should create a DM conversation', async () => {
|
|
17
|
+
dmConversation = await user1Caller.conversation.create({
|
|
18
|
+
type: 'DM',
|
|
19
|
+
memberIds: ['testuser2'],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(dmConversation).toBeDefined();
|
|
23
|
+
expect(dmConversation.type).toBe('DM');
|
|
24
|
+
expect(dmConversation.members.length).toBe(2);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should create a GROUP conversation', async () => {
|
|
28
|
+
groupConversation = await user1Caller.conversation.create({
|
|
29
|
+
type: 'GROUP',
|
|
30
|
+
name: 'Test Group Chat',
|
|
31
|
+
memberIds: [user3Id],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(groupConversation).toBeDefined();
|
|
35
|
+
expect(groupConversation.type).toBe('GROUP');
|
|
36
|
+
expect(groupConversation.name).toBe('Test Group Chat');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should fail to create duplicate DM', async () => {
|
|
40
|
+
await expect(
|
|
41
|
+
user1Caller.conversation.create({
|
|
42
|
+
type: 'DM',
|
|
43
|
+
memberIds: ['testuser2'],
|
|
44
|
+
}),
|
|
45
|
+
).rejects.toThrow();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('should fail to create GROUP without name', async () => {
|
|
49
|
+
await expect(
|
|
50
|
+
user1Caller.conversation.create({
|
|
51
|
+
type: 'GROUP',
|
|
52
|
+
memberIds: [user3Id],
|
|
53
|
+
}),
|
|
54
|
+
).rejects.toThrow();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('should fail to create DM with multiple members', async () => {
|
|
58
|
+
await expect(
|
|
59
|
+
user1Caller.conversation.create({
|
|
60
|
+
type: 'DM',
|
|
61
|
+
memberIds: ['testuser2', 'testuser3'],
|
|
62
|
+
}),
|
|
63
|
+
).rejects.toThrow();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('get', () => {
|
|
68
|
+
test('should get conversation by ID', async () => {
|
|
69
|
+
const conversation = await user1Caller.conversation.get({
|
|
70
|
+
conversationId: dmConversation.id,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(conversation).toBeDefined();
|
|
74
|
+
expect(conversation.id).toBe(dmConversation.id);
|
|
75
|
+
expect(conversation.members.length).toBe(2);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should fail for non-member', async () => {
|
|
79
|
+
await expect(
|
|
80
|
+
user3Caller.conversation.get({
|
|
81
|
+
conversationId: dmConversation.id,
|
|
82
|
+
}),
|
|
83
|
+
).rejects.toThrow();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('list', () => {
|
|
88
|
+
test('should list conversations for user', async () => {
|
|
89
|
+
const conversations = await user1Caller.conversation.list();
|
|
90
|
+
|
|
91
|
+
expect(Array.isArray(conversations)).toBe(true);
|
|
92
|
+
const ids = conversations.map((c) => c.id);
|
|
93
|
+
expect(ids).toContain(dmConversation.id);
|
|
94
|
+
expect(ids).toContain(groupConversation.id);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should not list conversations user is not in', async () => {
|
|
98
|
+
const conversations = await user3Caller.conversation.list();
|
|
99
|
+
const ids = conversations.map((c) => c.id);
|
|
100
|
+
expect(ids).not.toContain(dmConversation.id);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('addMember', () => {
|
|
105
|
+
test('should add member to group conversation', async () => {
|
|
106
|
+
await user1Caller.conversation.addMember({
|
|
107
|
+
conversationId: groupConversation.id,
|
|
108
|
+
memberUsername: 'testuser2',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const conversation = await user2Caller.conversation.get({
|
|
112
|
+
conversationId: groupConversation.id,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(conversation.members.length).toBe(3);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('should fail to add non-existent user', async () => {
|
|
119
|
+
await expect(
|
|
120
|
+
user1Caller.conversation.addMember({
|
|
121
|
+
conversationId: groupConversation.id,
|
|
122
|
+
memberUsername: 'nonexistentuser',
|
|
123
|
+
}),
|
|
124
|
+
).rejects.toThrow();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('removeMember', () => {
|
|
129
|
+
test('should remove member from conversation', async () => {
|
|
130
|
+
await user1Caller.conversation.removeMember({
|
|
131
|
+
conversationId: groupConversation.id,
|
|
132
|
+
memberId: user3Id,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await expect(
|
|
136
|
+
user3Caller.conversation.get({
|
|
137
|
+
conversationId: groupConversation.id,
|
|
138
|
+
}),
|
|
139
|
+
).rejects.toThrow();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('should fail without authentication', () =>
|
|
144
|
+
expectUnauth((c) => c.conversation.list()));
|
|
145
|
+
});
|