@studious-lms/server 1.4.7 → 1.5.0
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/.github/workflows/npm.yml +28 -0
- package/dist/lib/config/cors.d.ts.map +1 -1
- package/dist/lib/config/cors.js +2 -4
- package/dist/lib/config/cors.js.map +1 -1
- package/dist/lib/fileUpload.d.ts.map +1 -1
- package/dist/lib/fileUpload.js +4 -5
- package/dist/lib/fileUpload.js.map +1 -1
- package/dist/models/assignment.d.ts +32 -0
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/assignment.js +111 -2
- package/dist/models/assignment.js.map +1 -1
- package/dist/models/auth.d.ts +1 -0
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/auth.js +3 -2
- package/dist/models/auth.js.map +1 -1
- package/dist/models/comment.d.ts +0 -5
- package/dist/models/comment.d.ts.map +1 -1
- package/dist/models/conversation.d.ts +2 -1
- package/dist/models/conversation.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +2 -1
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/message.d.ts +6 -3
- package/dist/models/message.d.ts.map +1 -1
- package/dist/models/school.d.ts +0 -22
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +2 -14
- package/dist/models/school.js.map +1 -1
- package/dist/models/worksheet.d.ts +174 -95
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +38 -5
- package/dist/models/worksheet.js.map +1 -1
- package/dist/pipelines/aiLabChat.js +6 -6
- package/dist/pipelines/aiLabChat.js.map +1 -1
- package/dist/pipelines/gradeWorksheet.d.ts +5 -3
- package/dist/pipelines/gradeWorksheet.d.ts.map +1 -1
- package/dist/pipelines/gradeWorksheet.js +60 -24
- package/dist/pipelines/gradeWorksheet.js.map +1 -1
- package/dist/pipelines/pdfToWorksheet.d.ts +3 -0
- package/dist/pipelines/pdfToWorksheet.d.ts.map +1 -1
- package/dist/pipelines/pdfToWorksheet.js +65 -24
- package/dist/pipelines/pdfToWorksheet.js.map +1 -1
- package/dist/routers/_app.d.ts +232 -48
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/announcement.d.ts +0 -4
- package/dist/routers/announcement.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +35 -8
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +10 -3
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/auth.d.ts +1 -0
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/class.d.ts +19 -0
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/comment.d.ts +0 -3
- package/dist/routers/comment.d.ts.map +1 -1
- package/dist/routers/conversation.d.ts +2 -1
- package/dist/routers/conversation.d.ts.map +1 -1
- package/dist/routers/labChat.d.ts +2 -1
- package/dist/routers/labChat.d.ts.map +1 -1
- package/dist/routers/school.d.ts +6 -0
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +7 -2
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/worksheet.d.ts +51 -7
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +10 -3
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/services/assignment.d.ts +30 -0
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +37 -3
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/auth.d.ts +1 -0
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/class.d.ts +19 -0
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +37 -19
- package/dist/services/class.js.map +1 -1
- package/dist/services/comment.d.ts +0 -3
- package/dist/services/comment.d.ts.map +1 -1
- package/dist/services/conversation.d.ts +2 -1
- package/dist/services/conversation.d.ts.map +1 -1
- package/dist/services/labChat.d.ts +2 -1
- package/dist/services/labChat.d.ts.map +1 -1
- package/dist/services/labChat.js +6 -6
- package/dist/services/labChat.js.map +1 -1
- package/dist/services/message.d.ts.map +1 -1
- package/dist/services/message.js +14 -2
- package/dist/services/message.js.map +1 -1
- package/dist/services/school.d.ts +6 -0
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +71 -6
- package/dist/services/school.js.map +1 -1
- package/dist/services/worksheet.d.ts +46 -7
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +52 -20
- package/dist/services/worksheet.js.map +1 -1
- package/dist/utils/generateUsername.d.ts +8 -0
- package/dist/utils/generateUsername.d.ts.map +1 -0
- package/dist/utils/generateUsername.js +103 -0
- package/dist/utils/generateUsername.js.map +1 -0
- package/dist/workers/connection.d.ts +3 -0
- package/dist/workers/connection.d.ts.map +1 -0
- package/dist/workers/connection.js +33 -0
- package/dist/workers/connection.js.map +1 -0
- package/dist/workers/index.d.ts +10 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +53 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/processors/pdfToWorksheet.d.ts +9 -0
- package/dist/workers/processors/pdfToWorksheet.d.ts.map +1 -0
- package/dist/workers/processors/pdfToWorksheet.js +100 -0
- package/dist/workers/processors/pdfToWorksheet.js.map +1 -0
- package/dist/workers/queues.d.ts +69 -0
- package/dist/workers/queues.d.ts.map +1 -0
- package/dist/workers/queues.js +119 -0
- package/dist/workers/queues.js.map +1 -0
- package/package.json +4 -1
- package/prisma/migrations/20260504071500_school_memberships/migration.sql +1 -1
- package/prisma/migrations/20260506162500_consolidate_run_job_and_unify_job_status/migration.sql +166 -0
- package/prisma/schema.prisma +465 -387
- package/src/lib/config/cors.ts +0 -2
- package/src/lib/fileUpload.ts +2 -4
- package/src/models/assignment.ts +132 -0
- package/src/models/auth.ts +1 -0
- package/src/models/school.ts +0 -14
- package/src/models/worksheet.ts +56 -2
- package/src/pipelines/aiLabChat.ts +4 -4
- package/src/pipelines/gradeWorksheet.ts +63 -25
- package/src/pipelines/pdfToWorksheet.ts +68 -22
- package/src/routers/assignment.ts +19 -0
- package/src/routers/school.ts +5 -0
- package/src/routers/worksheet.ts +18 -0
- package/src/services/assignment.ts +47 -1
- package/src/services/class.ts +41 -17
- package/src/services/labChat.ts +4 -4
- package/src/services/message.ts +13 -0
- package/src/services/school.ts +89 -6
- package/src/services/worksheet.ts +62 -18
- package/src/utils/generateUsername.ts +108 -0
- package/src/workers/connection.ts +37 -0
- package/src/workers/index.ts +61 -0
- package/src/workers/processors/pdfToWorksheet.ts +119 -0
- package/src/workers/queues.ts +159 -0
- package/tests/{globalSetup.ts → integration/globalSetup.ts} +4 -4
- package/tests/{helpers.ts → integration/helpers.ts} +2 -2
- package/tests/{pipelines → integration/pipelines}/pdfToWorksheet.test.ts +55 -43
- package/tests/{routers → integration/routers}/attendance.test.ts +1 -1
- package/tests/{routers → integration/routers}/auth.test.ts +3 -3
- package/tests/{routers → integration/routers}/class.test.ts +3 -17
- package/tests/{routers → integration/routers}/conversation.test.ts +1 -1
- package/tests/{routers → integration/routers}/event.test.ts +1 -1
- package/tests/{routers → integration/routers}/message.test.ts +1 -1
- package/tests/{routers → integration/routers}/newtonChat.test.ts +1 -1
- package/tests/{routers → integration/routers}/studentProgress.test.ts +1 -1
- package/tests/{user.test.ts → integration/routers/user.test.ts} +2 -2
- package/tests/integration/routers/worksheet.test.ts +448 -0
- package/tests/{setup.ts → integration/setup.ts} +4 -4
- package/tests/{lib → unit/lib}/cors.test.ts +1 -1
- package/tests/{middleware → unit/middleware}/security.test.ts +1 -1
- package/tests/{pipelines → unit/pipelines}/aiLabChat.test.ts +1 -1
- package/tests/{lib → unit/pipelines}/aiLabChatContract.test.ts +1 -1
- package/tests/{utils/aiLabChatPrompt.test.ts → unit/pipelines/labChatPrompt.test.ts} +2 -2
- package/tests/{utils → unit/services}/studentProgress.test.ts +5 -5
- package/tests/{utils → unit/utils}/aiUser.test.ts +1 -1
- package/tests/{utils → unit/utils}/generateInviteCode.test.ts +1 -1
- package/tests/{utils → unit/utils}/inference.test.ts +6 -6
- package/tests/{utils → unit/utils}/logger.test.ts +1 -1
- package/tests/{utils → unit/utils}/prismaErrorHandler.test.ts +1 -1
- package/tests/{utils → unit/utils}/prismaWrapper.test.ts +1 -1
- package/vitest.config.ts +11 -3
- package/vitest.unit.config.ts +10 -10
- package/dist/lib/notificationHandler.d.ts +0 -25
- package/dist/lib/notificationHandler.d.ts.map +0 -1
- package/dist/lib/notificationHandler.js +0 -32
- package/dist/lib/notificationHandler.js.map +0 -1
- package/dist/server/pipelines/aiLabChat.d.ts +0 -21
- package/dist/server/pipelines/aiLabChat.d.ts.map +0 -1
- package/dist/server/pipelines/aiLabChat.js +0 -456
- package/dist/server/pipelines/aiLabChat.js.map +0 -1
- package/dist/server/pipelines/aiNewtonChat.d.ts +0 -30
- package/dist/server/pipelines/aiNewtonChat.d.ts.map +0 -1
- package/dist/server/pipelines/aiNewtonChat.js +0 -285
- package/dist/server/pipelines/aiNewtonChat.js.map +0 -1
- package/dist/server/pipelines/gradeWorksheet.d.ts +0 -30
- package/dist/server/pipelines/gradeWorksheet.d.ts.map +0 -1
- package/dist/server/pipelines/gradeWorksheet.js +0 -248
- package/dist/server/pipelines/gradeWorksheet.js.map +0 -1
- package/tests/routers/worksheet.test.ts +0 -194
- package/tests/startup.test.ts +0 -5
- package/tests/worksheet.test.ts +0 -181
- /package/tests/{routers → integration/routers}/agenda.test.ts +0 -0
- /package/tests/{routers → integration/routers}/announcement.test.ts +0 -0
- /package/tests/{routers → integration/routers}/assignment.test.ts +0 -0
- /package/tests/{routers → integration/routers}/comment.test.ts +0 -0
- /package/tests/{routers → integration/routers}/folder.test.ts +0 -0
- /package/tests/{routers → integration/routers}/labChat.test.ts +0 -0
- /package/tests/{routers → integration/routers}/marketing.test.ts +0 -0
- /package/tests/{routers → integration/routers}/notification.test.ts +0 -0
- /package/tests/{routers → integration/routers}/search.test.ts +0 -0
- /package/tests/{routers → integration/routers}/section.test.ts +0 -0
- /package/tests/{server → unit/middleware}/rateLimit.test.ts +0 -0
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5fcc36af-16e5-52cb-84e1-f2f423e6d9eb")}catch(e){}}();
|
|
3
|
-
import { GenerationStatus, WorksheetQuestionType } from "@prisma/client";
|
|
4
|
-
import { prisma } from "../../lib/prisma.js";
|
|
5
|
-
import { logger } from "../../utils/logger.js";
|
|
6
|
-
import z from "zod";
|
|
7
|
-
import { inference } from "../../utils/inference.js";
|
|
8
|
-
import { getAIUserId } from "../../utils/aiUser.js";
|
|
9
|
-
import { pusher } from "../../lib/pusher.js";
|
|
10
|
-
const removeAllPreviousAIComments = async (worksheetQuestionProgressId) => {
|
|
11
|
-
await prisma.comment.deleteMany({
|
|
12
|
-
where: {
|
|
13
|
-
studentQuestionProgressId: worksheetQuestionProgressId,
|
|
14
|
-
authorId: getAIUserId(),
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
|
-
const gradeWorksheetQuestion = async (worksheetResponseId, worksheetQuestionProgressId) => {
|
|
19
|
-
const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
|
|
20
|
-
where: { id: worksheetResponseId },
|
|
21
|
-
include: {
|
|
22
|
-
worksheet: true,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
if (!worksheetResponse) {
|
|
26
|
-
logger.error('Worksheet response not found');
|
|
27
|
-
throw new Error('Worksheet response not found');
|
|
28
|
-
}
|
|
29
|
-
const studentQuestionProgress = await prisma.studentQuestionProgress.findFirst({
|
|
30
|
-
where: {
|
|
31
|
-
id: worksheetQuestionProgressId,
|
|
32
|
-
},
|
|
33
|
-
include: {
|
|
34
|
-
question: true,
|
|
35
|
-
comments: true,
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
if (!studentQuestionProgress) {
|
|
39
|
-
const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.create({
|
|
40
|
-
data: {
|
|
41
|
-
studentId: worksheetResponse.studentId,
|
|
42
|
-
questionId: worksheetQuestionProgressId,
|
|
43
|
-
response: '',
|
|
44
|
-
isCorrect: false,
|
|
45
|
-
markschemeState: {},
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
return updatedStudentQuestionProgress;
|
|
49
|
-
}
|
|
50
|
-
pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-pending`, {
|
|
51
|
-
id: studentQuestionProgress.id,
|
|
52
|
-
});
|
|
53
|
-
const question = studentQuestionProgress.question;
|
|
54
|
-
const comments = studentQuestionProgress.comments;
|
|
55
|
-
const responseText = studentQuestionProgress.response;
|
|
56
|
-
try {
|
|
57
|
-
const apiResponse = await inference(`Grade the following worksheet response:
|
|
58
|
-
|
|
59
|
-
Question: ${question.question}
|
|
60
|
-
Response: ${responseText}
|
|
61
|
-
|
|
62
|
-
Comments: ${comments.map((comment) => comment.content).join('\n')}
|
|
63
|
-
Mark Scheme: ${JSON.stringify(question.markScheme)}
|
|
64
|
-
|
|
65
|
-
Justify your reasoning by including comment(s) and mark the question please.
|
|
66
|
-
Return ONLY JSON in the following format (fill in the values as per the question):
|
|
67
|
-
{
|
|
68
|
-
"isCorrect": <boolean>,
|
|
69
|
-
"points": <number>,
|
|
70
|
-
"markschemeState": [
|
|
71
|
-
{ "id": <string>, "correct": <boolean> }
|
|
72
|
-
],
|
|
73
|
-
"comments": [<string>, ...]
|
|
74
|
-
}
|
|
75
|
-
`, z.object({
|
|
76
|
-
isCorrect: z.boolean(),
|
|
77
|
-
points: z.number(),
|
|
78
|
-
markschemeState: z.array(z.object({
|
|
79
|
-
id: z.string(),
|
|
80
|
-
correct: z.boolean(),
|
|
81
|
-
})), // @note: this has to be converted to [id: string]: correct boolean
|
|
82
|
-
comments: z.array(z.string()),
|
|
83
|
-
})).catch((error) => {
|
|
84
|
-
logger.error('Failed to grade worksheet response', { error });
|
|
85
|
-
throw error;
|
|
86
|
-
});
|
|
87
|
-
const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({
|
|
88
|
-
where: { id: studentQuestionProgress.id, status: {
|
|
89
|
-
not: {
|
|
90
|
-
in: ['CANCELLED'],
|
|
91
|
-
},
|
|
92
|
-
} },
|
|
93
|
-
data: {
|
|
94
|
-
status: GenerationStatus.COMPLETED,
|
|
95
|
-
isCorrect: apiResponse.isCorrect,
|
|
96
|
-
points: apiResponse.points,
|
|
97
|
-
markschemeState: apiResponse.markschemeState.reduce((acc, curr) => {
|
|
98
|
-
acc["item-" + curr.id] = curr.correct;
|
|
99
|
-
return acc;
|
|
100
|
-
}, {}),
|
|
101
|
-
comments: {
|
|
102
|
-
create: apiResponse.comments.map((commentContent) => ({
|
|
103
|
-
content: commentContent,
|
|
104
|
-
authorId: getAIUserId(),
|
|
105
|
-
})),
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-completed`, {
|
|
110
|
-
id: updatedStudentQuestionProgress.id,
|
|
111
|
-
});
|
|
112
|
-
return updatedStudentQuestionProgress;
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
logger.error('Failed to grade worksheet response', { error, worksheetResponseId });
|
|
116
|
-
pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-failed`, {
|
|
117
|
-
id: studentQuestionProgress.id,
|
|
118
|
-
});
|
|
119
|
-
await prisma.studentQuestionProgress.update({
|
|
120
|
-
where: { id: studentQuestionProgress.id },
|
|
121
|
-
data: { status: GenerationStatus.FAILED },
|
|
122
|
-
});
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
/**
|
|
127
|
-
* Grades and regrades worksheet (can fixed failed responses)
|
|
128
|
-
* @param worksheetResponseId worksheet response id
|
|
129
|
-
* @returns updated worksheet response
|
|
130
|
-
*/
|
|
131
|
-
const DO_NOT_INFERENCE_STATUSES = [GenerationStatus.CANCELLED, GenerationStatus.PENDING, GenerationStatus.COMPLETED];
|
|
132
|
-
export const gradeWorksheetPipeline = async (worksheetResponseId) => {
|
|
133
|
-
logger.info('Grading worksheet response', { worksheetResponseId });
|
|
134
|
-
const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
|
|
135
|
-
where: { id: worksheetResponseId },
|
|
136
|
-
include: {
|
|
137
|
-
worksheet: true,
|
|
138
|
-
responses: {
|
|
139
|
-
where: {
|
|
140
|
-
status: {
|
|
141
|
-
not: {
|
|
142
|
-
in: DO_NOT_INFERENCE_STATUSES,
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
question: {
|
|
146
|
-
type: {
|
|
147
|
-
not: {
|
|
148
|
-
in: [WorksheetQuestionType.MULTIPLE_CHOICE, WorksheetQuestionType.TRUE_FALSE],
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
include: {
|
|
154
|
-
question: true,
|
|
155
|
-
comments: true,
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
if (!worksheetResponse) {
|
|
161
|
-
logger.error('Worksheet response not found');
|
|
162
|
-
throw new Error('Worksheet response not found');
|
|
163
|
-
}
|
|
164
|
-
// Use for...of instead of forEach to properly handle async operations
|
|
165
|
-
for (const response of worksheetResponse.responses) {
|
|
166
|
-
logger.info('Grading question', { questionId: response.questionId });
|
|
167
|
-
const studentQuestionProgress = await prisma.studentQuestionProgress.update({
|
|
168
|
-
where: { id: response.id, status: {
|
|
169
|
-
not: {
|
|
170
|
-
in: DO_NOT_INFERENCE_STATUSES,
|
|
171
|
-
}
|
|
172
|
-
} },
|
|
173
|
-
data: { status: GenerationStatus.PENDING },
|
|
174
|
-
});
|
|
175
|
-
if (studentQuestionProgress.status !== GenerationStatus.PENDING) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
gradeWorksheetQuestion(worksheetResponseId, response.id);
|
|
179
|
-
}
|
|
180
|
-
;
|
|
181
|
-
};
|
|
182
|
-
export const cancelGradePipeline = async (worksheetResponseId, worksheetQuestionProgressId) => {
|
|
183
|
-
logger.info('Cancelling auto grading', { worksheetResponseId, worksheetQuestionProgressId });
|
|
184
|
-
const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
|
|
185
|
-
where: { id: worksheetResponseId },
|
|
186
|
-
include: {
|
|
187
|
-
worksheet: true,
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
if (!worksheetResponse) {
|
|
191
|
-
logger.error('Worksheet response not found');
|
|
192
|
-
throw new Error('Worksheet response not found');
|
|
193
|
-
}
|
|
194
|
-
const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({
|
|
195
|
-
where: { id: worksheetQuestionProgressId },
|
|
196
|
-
data: { status: GenerationStatus.CANCELLED },
|
|
197
|
-
});
|
|
198
|
-
await removeAllPreviousAIComments(worksheetQuestionProgressId);
|
|
199
|
-
pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-cancelled`, {
|
|
200
|
-
id: updatedStudentQuestionProgress.id,
|
|
201
|
-
});
|
|
202
|
-
return updatedStudentQuestionProgress;
|
|
203
|
-
};
|
|
204
|
-
export const regradeWorksheetPipeline = async (worksheetResponseId, worksheetQuestionProgressId) => {
|
|
205
|
-
logger.info('Regrading worksheet response', { worksheetResponseId, worksheetQuestionProgressId });
|
|
206
|
-
try {
|
|
207
|
-
const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
|
|
208
|
-
where: { id: worksheetResponseId, },
|
|
209
|
-
include: {
|
|
210
|
-
worksheet: true,
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
await removeAllPreviousAIComments(worksheetQuestionProgressId);
|
|
214
|
-
if (!worksheetResponse) {
|
|
215
|
-
logger.error('Worksheet response not found');
|
|
216
|
-
throw new Error('Worksheet response not found');
|
|
217
|
-
}
|
|
218
|
-
const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({
|
|
219
|
-
where: { id: worksheetQuestionProgressId },
|
|
220
|
-
data: { status: GenerationStatus.PENDING },
|
|
221
|
-
});
|
|
222
|
-
gradeWorksheetQuestion(worksheetResponseId, worksheetQuestionProgressId);
|
|
223
|
-
return updatedStudentQuestionProgress;
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
await prisma.studentQuestionProgress.update({
|
|
227
|
-
where: { id: worksheetQuestionProgressId },
|
|
228
|
-
data: { status: GenerationStatus.FAILED },
|
|
229
|
-
});
|
|
230
|
-
const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({
|
|
231
|
-
where: { id: worksheetResponseId, },
|
|
232
|
-
include: {
|
|
233
|
-
worksheet: true,
|
|
234
|
-
},
|
|
235
|
-
});
|
|
236
|
-
if (!worksheetResponse) {
|
|
237
|
-
logger.error('Worksheet response not found');
|
|
238
|
-
throw new Error('Worksheet response not found');
|
|
239
|
-
}
|
|
240
|
-
pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-failed`, {
|
|
241
|
-
id: worksheetQuestionProgressId,
|
|
242
|
-
});
|
|
243
|
-
logger.error('Failed to regrade worksheet response', { error, worksheetResponseId, worksheetQuestionProgressId });
|
|
244
|
-
throw error;
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
//# sourceMappingURL=gradeWorksheet.js.map
|
|
248
|
-
//# debugId=5fcc36af-16e5-52cb-84e1-f2f423e6d9eb
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gradeWorksheet.js","sources":["server/pipelines/gradeWorksheet.ts"],"sourceRoot":"/","sourcesContent":["import { GenerationStatus, WorksheetQuestionType } from \"@prisma/client\";\nimport { prisma } from \"../../lib/prisma.js\";\nimport { logger } from \"../../utils/logger.js\";\nimport z from \"zod\";\nimport { inference } from \"../../utils/inference.js\";\nimport { getAIUserId } from \"../../utils/aiUser.js\";\nimport { pusher } from \"../../lib/pusher.js\";\n\n\nconst removeAllPreviousAIComments = async (worksheetQuestionProgressId: string) => {\n await prisma.comment.deleteMany({\n where: {\n studentQuestionProgressId: worksheetQuestionProgressId,\n authorId: getAIUserId(),\n },\n });\n};\n\nconst gradeWorksheetQuestion = async (worksheetResponseId: string, worksheetQuestionProgressId: string) => {\n\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n },\n });\n\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n } \n\n const studentQuestionProgress = await prisma.studentQuestionProgress.findFirst({\n where: {\n id: worksheetQuestionProgressId,\n },\n include: {\n question: true,\n comments: true,\n },\n });\n\n if (!studentQuestionProgress) {\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.create({\n data: {\n studentId: worksheetResponse.studentId,\n questionId: worksheetQuestionProgressId,\n response: '',\n isCorrect: false,\n markschemeState: {},\n },\n });\n\n return updatedStudentQuestionProgress;\n }\n\n pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-pending`, {\n id: studentQuestionProgress.id,\n });\n\n const question = studentQuestionProgress.question;\n const comments = studentQuestionProgress.comments;\n const responseText = studentQuestionProgress.response;\n\n\n try {\n const apiResponse = await inference(\n `Grade the following worksheet response:\n \n Question: ${question.question}\n Response: ${responseText}\n\n Comments: ${comments.map((comment) => comment.content).join('\\n')}\n Mark Scheme: ${JSON.stringify(question.markScheme)}\n \n Justify your reasoning by including comment(s) and mark the question please. \n Return ONLY JSON in the following format (fill in the values as per the question):\n {\n \"isCorrect\": <boolean>,\n \"points\": <number>,\n \"markschemeState\": [\n { \"id\": <string>, \"correct\": <boolean> }\n ],\n \"comments\": [<string>, ...]\n }\n `,\n z.object({\n isCorrect: z.boolean(),\n points: z.number(),\n markschemeState: z.array(z.object({\n id: z.string(),\n correct: z.boolean(),\n })), // @note: this has to be converted to [id: string]: correct boolean\n comments: z.array(z.string()),\n }),\n ).catch((error) => {\n logger.error('Failed to grade worksheet response', { error });\n throw error;\n });\n\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({\n where: { id: studentQuestionProgress.id, status: {\n not: {\n in: ['CANCELLED'],\n },\n } },\n data: {\n status: GenerationStatus.COMPLETED,\n isCorrect: (apiResponse as { isCorrect: boolean }).isCorrect,\n points: (apiResponse as { points: number }).points,\n markschemeState: (apiResponse as {\n markschemeState: { id: string; correct: boolean }[];\n }).markschemeState.reduce((acc, curr) => {\n acc[\"item-\" + curr.id] = curr.correct;\n return acc;\n }, {} as Record<string, boolean>),\n comments: {\n create: (apiResponse as {\n comments: string[];\n }).comments.map((commentContent) => ({\n content: commentContent,\n authorId: getAIUserId(),\n })),\n },\n },\n });\n pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-completed`, {\n id: updatedStudentQuestionProgress.id,\n });\n\n return updatedStudentQuestionProgress;\n } catch (error) {\n logger.error('Failed to grade worksheet response', { error, worksheetResponseId });\n pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-failed`, {\n id: studentQuestionProgress.id,\n });\n await prisma.studentQuestionProgress.update({\n where: { id: studentQuestionProgress.id },\n data: { status: GenerationStatus.FAILED },\n });\n throw error;\n }\n}\n\n/**\n * Grades and regrades worksheet (can fixed failed responses)\n * @param worksheetResponseId worksheet response id\n * @returns updated worksheet response\n */\n\nconst DO_NOT_INFERENCE_STATUSES = [GenerationStatus.CANCELLED, GenerationStatus.PENDING, GenerationStatus.COMPLETED];\n\nexport const gradeWorksheetPipeline = async (worksheetResponseId: string) => {\n logger.info('Grading worksheet response', { worksheetResponseId });\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n responses: {\n where: {\n status: {\n not: {\n in: DO_NOT_INFERENCE_STATUSES,\n },\n },\n question: {\n type: {\n not: {\n in: [WorksheetQuestionType.MULTIPLE_CHOICE, WorksheetQuestionType.TRUE_FALSE],\n }\n },\n },\n },\n include: {\n question: true,\n comments: true,\n },\n },\n },\n });\n\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n\n // Use for...of instead of forEach to properly handle async operations\n for (const response of worksheetResponse.responses) {\n logger.info('Grading question', { questionId: response.questionId });\n\n const studentQuestionProgress = await prisma.studentQuestionProgress.update({\n where: { id: response.id, status: {\n not: {\n in: DO_NOT_INFERENCE_STATUSES,\n }\n } },\n data: { status: GenerationStatus.PENDING },\n });\n\n if (studentQuestionProgress.status !== GenerationStatus.PENDING) {\n return;\n }\n\n gradeWorksheetQuestion(worksheetResponseId, response.id);\n\n };\n};\n\nexport const cancelGradePipeline = async (worksheetResponseId: string, worksheetQuestionProgressId: string) => {\n logger.info('Cancelling auto grading', { worksheetResponseId, worksheetQuestionProgressId });\n\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId },\n include: {\n worksheet: true,\n },\n });\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({\n where: { id: worksheetQuestionProgressId },\n data: { status: GenerationStatus.CANCELLED },\n });\n\n await removeAllPreviousAIComments(worksheetQuestionProgressId);\n\n pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-cancelled`, {\n id: updatedStudentQuestionProgress.id,\n });\n\n return updatedStudentQuestionProgress;\n};\n\nexport const regradeWorksheetPipeline = async (worksheetResponseId: string, worksheetQuestionProgressId: string) => {\n logger.info('Regrading worksheet response', { worksheetResponseId, worksheetQuestionProgressId });\n try {\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId, },\n include: {\n worksheet: true,\n },\n });\n \n await removeAllPreviousAIComments(worksheetQuestionProgressId);\n\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n\n const updatedStudentQuestionProgress = await prisma.studentQuestionProgress.update({\n where: { id: worksheetQuestionProgressId },\n data: { status: GenerationStatus.PENDING },\n });\n\n gradeWorksheetQuestion(worksheetResponseId, worksheetQuestionProgressId);\n\n return updatedStudentQuestionProgress;\n } catch (error) {\n await prisma.studentQuestionProgress.update({\n where: { id: worksheetQuestionProgressId },\n data: { status: GenerationStatus.FAILED },\n });\n const worksheetResponse = await prisma.studentWorksheetResponse.findUnique({\n where: { id: worksheetResponseId, },\n include: {\n worksheet: true,\n },\n });\n if (!worksheetResponse) {\n logger.error('Worksheet response not found');\n throw new Error('Worksheet response not found');\n }\n pusher.trigger(`class-${worksheetResponse.worksheet.classId}-worksheetSubmission-${worksheetResponse.id}`, `set-failed`, {\n id: worksheetQuestionProgressId,\n });\n logger.error('Failed to regrade worksheet response', { error, worksheetResponseId, worksheetQuestionProgressId });\n throw error;\n }\n};\n"],"names":[],"mappings":"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","debug_id":"5fcc36af-16e5-52cb-84e1-f2f423e6d9eb"}
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, test } from "vitest";
|
|
2
|
-
import { user1Caller, user2Caller, user3Caller } from "../setup";
|
|
3
|
-
|
|
4
|
-
const expectTRPCError = async (promise: Promise<unknown>, code: string) => {
|
|
5
|
-
await expect(promise).rejects.toMatchObject({ code });
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
describe("Worksheet Router Security", () => {
|
|
9
|
-
let testClass: any;
|
|
10
|
-
let worksheet: any;
|
|
11
|
-
let question: any;
|
|
12
|
-
let multipleChoiceQuestion: any;
|
|
13
|
-
let submission: any;
|
|
14
|
-
let worksheetResponse: any;
|
|
15
|
-
let progressId: string;
|
|
16
|
-
|
|
17
|
-
beforeEach(async () => {
|
|
18
|
-
testClass = await user1Caller.class.create({
|
|
19
|
-
name: `Worksheet Security ${Date.now()}`,
|
|
20
|
-
subject: "Physics",
|
|
21
|
-
section: "12th Grade",
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const user2Profile = await user2Caller.user.getProfile();
|
|
25
|
-
await user1Caller.class.addStudent({
|
|
26
|
-
classId: testClass.id,
|
|
27
|
-
studentId: user2Profile.id,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
worksheet = await user1Caller.worksheet.create({
|
|
31
|
-
classId: testClass.id,
|
|
32
|
-
name: "Security Worksheet",
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
question = await user1Caller.worksheet.addQuestion({
|
|
36
|
-
worksheetId: worksheet.id,
|
|
37
|
-
question: "What is force?",
|
|
38
|
-
answer: "Mass times acceleration",
|
|
39
|
-
points: 5,
|
|
40
|
-
type: "SHORT_ANSWER",
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
multipleChoiceQuestion = await user1Caller.worksheet.addQuestion({
|
|
44
|
-
worksheetId: worksheet.id,
|
|
45
|
-
question: "What is the SI unit of force?",
|
|
46
|
-
answer: "newton",
|
|
47
|
-
points: 1,
|
|
48
|
-
type: "MULTIPLE_CHOICE",
|
|
49
|
-
options: [
|
|
50
|
-
{ id: "a", text: "Joule", isCorrect: false },
|
|
51
|
-
{ id: "b", text: "Newton", isCorrect: true },
|
|
52
|
-
],
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const dueDate = new Date();
|
|
56
|
-
dueDate.setDate(dueDate.getDate() + 7);
|
|
57
|
-
|
|
58
|
-
const assignment = await user1Caller.assignment.create({
|
|
59
|
-
classId: testClass.id,
|
|
60
|
-
title: "Worksheet Assignment",
|
|
61
|
-
instructions: "Complete the worksheet.",
|
|
62
|
-
dueDate: dueDate.toISOString(),
|
|
63
|
-
acceptWorksheet: true,
|
|
64
|
-
gradeWithAI: true,
|
|
65
|
-
worksheetIds: [worksheet.id],
|
|
66
|
-
type: "HOMEWORK",
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
submission = await user2Caller.assignment.getSubmission({
|
|
70
|
-
classId: testClass.id,
|
|
71
|
-
assignmentId: assignment.id,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
worksheetResponse = await user2Caller.worksheet.getWorksheetSubmission({
|
|
75
|
-
worksheetId: worksheet.id,
|
|
76
|
-
submissionId: submission.id,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const answered = await user2Caller.worksheet.answerQuestion({
|
|
80
|
-
worksheetResponseId: worksheetResponse.id,
|
|
81
|
-
questionId: question.id,
|
|
82
|
-
response: "Mass times acceleration",
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
progressId = answered.responses[0].id;
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("redacts worksheet answers and correctness metadata for students", async () => {
|
|
89
|
-
const studentWorksheet = await user2Caller.worksheet.getWorksheet({
|
|
90
|
-
worksheetId: worksheet.id,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const shortAnswerQuestion = studentWorksheet.questions.find(
|
|
94
|
-
(item: any) => item.id === question.id,
|
|
95
|
-
);
|
|
96
|
-
expect(shortAnswerQuestion.answer).toBe("");
|
|
97
|
-
expect(shortAnswerQuestion.markScheme).toBeNull();
|
|
98
|
-
|
|
99
|
-
const studentMcq = studentWorksheet.questions.find(
|
|
100
|
-
(item: any) => item.id === multipleChoiceQuestion.id,
|
|
101
|
-
);
|
|
102
|
-
expect(studentMcq.answer).toBe("");
|
|
103
|
-
expect(studentMcq.options).toEqual([
|
|
104
|
-
{ id: "a", text: "Joule" },
|
|
105
|
-
{ id: "b", text: "Newton" },
|
|
106
|
-
]);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("blocks cross-class worksheet reads by id", async () => {
|
|
110
|
-
await expectTRPCError(
|
|
111
|
-
user3Caller.worksheet.getWorksheet({ worksheetId: worksheet.id }),
|
|
112
|
-
"FORBIDDEN",
|
|
113
|
-
);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("blocks cross-class worksheet submission reads by id", async () => {
|
|
117
|
-
await expectTRPCError(
|
|
118
|
-
user3Caller.worksheet.getWorksheetSubmission({
|
|
119
|
-
worksheetId: worksheet.id,
|
|
120
|
-
submissionId: submission.id,
|
|
121
|
-
}),
|
|
122
|
-
"FORBIDDEN",
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("blocks cross-class worksheet answer mutations", async () => {
|
|
127
|
-
await expectTRPCError(
|
|
128
|
-
user3Caller.worksheet.answerQuestion({
|
|
129
|
-
worksheetResponseId: worksheetResponse.id,
|
|
130
|
-
questionId: question.id,
|
|
131
|
-
response: "Unauthorized answer",
|
|
132
|
-
}),
|
|
133
|
-
"FORBIDDEN",
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("blocks students from teacher worksheet mutations", async () => {
|
|
138
|
-
await expectTRPCError(
|
|
139
|
-
user2Caller.worksheet.updateWorksheet({
|
|
140
|
-
worksheetId: worksheet.id,
|
|
141
|
-
name: "Student update",
|
|
142
|
-
}),
|
|
143
|
-
"FORBIDDEN",
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
await expectTRPCError(
|
|
147
|
-
user2Caller.worksheet.addQuestion({
|
|
148
|
-
worksheetId: worksheet.id,
|
|
149
|
-
question: "Student-created question",
|
|
150
|
-
answer: "Nope",
|
|
151
|
-
type: "SHORT_ANSWER",
|
|
152
|
-
}),
|
|
153
|
-
"FORBIDDEN",
|
|
154
|
-
);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("blocks students from grading and feedback actions", async () => {
|
|
158
|
-
await expectTRPCError(
|
|
159
|
-
user2Caller.worksheet.gradeAnswer({
|
|
160
|
-
questionId: question.id,
|
|
161
|
-
responseId: progressId,
|
|
162
|
-
studentWorksheetResponseId: worksheetResponse.id,
|
|
163
|
-
isCorrect: true,
|
|
164
|
-
feedback: "Nice work",
|
|
165
|
-
points: 5,
|
|
166
|
-
}),
|
|
167
|
-
"FORBIDDEN",
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
await expectTRPCError(
|
|
171
|
-
user2Caller.worksheet.addComment({
|
|
172
|
-
responseId: progressId,
|
|
173
|
-
comment: "Teacher feedback",
|
|
174
|
-
}),
|
|
175
|
-
"FORBIDDEN",
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
await expectTRPCError(
|
|
179
|
-
user2Caller.worksheet.cancelGrading({
|
|
180
|
-
worksheetResponseId: worksheetResponse.id,
|
|
181
|
-
progressId,
|
|
182
|
-
}),
|
|
183
|
-
"FORBIDDEN",
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
await expectTRPCError(
|
|
187
|
-
user2Caller.worksheet.regradeQuestion({
|
|
188
|
-
worksheetResponseId: worksheetResponse.id,
|
|
189
|
-
progressId,
|
|
190
|
-
}),
|
|
191
|
-
"FORBIDDEN",
|
|
192
|
-
);
|
|
193
|
-
});
|
|
194
|
-
});
|
package/tests/startup.test.ts
DELETED
package/tests/worksheet.test.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
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
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|