=test_update_homework_tests_and_work_log
Some checks failed
CI / build-deploy (push) Has been cancelled
Some checks failed
CI / build-deploy (push) Has been cancelled
This commit is contained in:
290
tests/integration/homework-actions.test.ts
Normal file
290
tests/integration/homework-actions.test.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const authMock = vi.fn()
|
||||
const revalidatePathMock = vi.fn()
|
||||
const createIdMock = vi.fn()
|
||||
|
||||
const ensureLimitMock = vi.fn()
|
||||
const ensureWhereMock = vi.fn(() => ({ limit: ensureLimitMock }))
|
||||
const ensureInnerJoinSecondMock = vi.fn(() => ({ where: ensureWhereMock }))
|
||||
const ensureInnerJoinFirstMock = vi.fn(() => ({ innerJoin: ensureInnerJoinSecondMock }))
|
||||
const countWhereMock = vi.fn()
|
||||
const selectFromMock = vi.fn(() => ({ innerJoin: ensureInnerJoinFirstMock, where: countWhereMock }))
|
||||
const selectMock = vi.fn(() => ({ from: selectFromMock }))
|
||||
|
||||
const assignmentFindFirstMock = vi.fn()
|
||||
const assignmentTargetFindFirstMock = vi.fn()
|
||||
const submissionFindFirstMock = vi.fn()
|
||||
const txAnswerFindFirstMock = vi.fn()
|
||||
|
||||
const insertValuesMock = vi.fn()
|
||||
const insertMock = vi.fn(() => ({ values: insertValuesMock }))
|
||||
const txInsertValuesMock = vi.fn()
|
||||
const txInsertMock = vi.fn(() => ({ values: txInsertValuesMock }))
|
||||
|
||||
const updateWhereMock = vi.fn()
|
||||
const updateSetMock = vi.fn(() => ({ where: updateWhereMock }))
|
||||
const updateMock = vi.fn(() => ({ set: updateSetMock }))
|
||||
const txUpdateWhereMock = vi.fn()
|
||||
const txUpdateSetMock = vi.fn(() => ({ where: txUpdateWhereMock }))
|
||||
const txUpdateMock = vi.fn(() => ({ set: txUpdateSetMock }))
|
||||
const transactionMock = vi.fn(async (callback: (tx: unknown) => Promise<unknown>) =>
|
||||
callback({
|
||||
query: { homeworkAnswers: { findFirst: txAnswerFindFirstMock } },
|
||||
update: txUpdateMock,
|
||||
insert: txInsertMock,
|
||||
})
|
||||
)
|
||||
|
||||
return {
|
||||
authMock,
|
||||
revalidatePathMock,
|
||||
createIdMock,
|
||||
ensureLimitMock,
|
||||
countWhereMock,
|
||||
selectMock,
|
||||
assignmentFindFirstMock,
|
||||
assignmentTargetFindFirstMock,
|
||||
submissionFindFirstMock,
|
||||
txAnswerFindFirstMock,
|
||||
insertValuesMock,
|
||||
insertMock,
|
||||
txInsertValuesMock,
|
||||
txInsertMock,
|
||||
updateSetMock,
|
||||
updateMock,
|
||||
txUpdateSetMock,
|
||||
txUpdateMock,
|
||||
transactionMock,
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("@/auth", () => ({
|
||||
auth: mocks.authMock,
|
||||
}))
|
||||
|
||||
vi.mock("next/cache", () => ({
|
||||
revalidatePath: mocks.revalidatePathMock,
|
||||
}))
|
||||
|
||||
vi.mock("@paralleldrive/cuid2", () => ({
|
||||
createId: mocks.createIdMock,
|
||||
}))
|
||||
|
||||
vi.mock("@/shared/db", () => ({
|
||||
db: {
|
||||
select: mocks.selectMock,
|
||||
query: {
|
||||
homeworkAssignments: { findFirst: mocks.assignmentFindFirstMock },
|
||||
homeworkAssignmentTargets: { findFirst: mocks.assignmentTargetFindFirstMock },
|
||||
homeworkSubmissions: { findFirst: mocks.submissionFindFirstMock },
|
||||
},
|
||||
insert: mocks.insertMock,
|
||||
update: mocks.updateMock,
|
||||
transaction: mocks.transactionMock,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("@/shared/db/schema", () => ({
|
||||
classes: { id: "id", teacherId: "teacherId" },
|
||||
classEnrollments: { classId: "classId", studentId: "studentId", status: "status" },
|
||||
classSubjectTeachers: { classId: "classId", teacherId: "teacherId", subjectId: "subjectId" },
|
||||
exams: { id: "id" },
|
||||
homeworkAnswers: { id: "id", submissionId: "submissionId", questionId: "questionId" },
|
||||
homeworkAssignmentQuestions: { assignmentId: "assignmentId" },
|
||||
homeworkAssignmentTargets: { assignmentId: "assignmentId", studentId: "studentId" },
|
||||
homeworkAssignments: { id: "id", status: "status" },
|
||||
homeworkSubmissions: {
|
||||
id: "id",
|
||||
assignmentId: "assignmentId",
|
||||
studentId: "studentId",
|
||||
status: "status",
|
||||
submittedAt: "submittedAt",
|
||||
isLate: "isLate",
|
||||
updatedAt: "updatedAt",
|
||||
score: "score",
|
||||
},
|
||||
roles: { id: "id", name: "name" },
|
||||
users: { id: "id" },
|
||||
usersToRoles: { userId: "userId", roleId: "roleId" },
|
||||
}))
|
||||
|
||||
import {
|
||||
gradeHomeworkSubmissionAction,
|
||||
saveHomeworkAnswerAction,
|
||||
startHomeworkSubmissionAction,
|
||||
submitHomeworkAction,
|
||||
} from "@/modules/homework/actions"
|
||||
|
||||
describe("homework action flow", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
})
|
||||
|
||||
it("starts submission for assigned student", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_student" } })
|
||||
mocks.ensureLimitMock.mockResolvedValue([{ id: "u_student" }])
|
||||
mocks.assignmentFindFirstMock.mockResolvedValue({
|
||||
id: "a_1",
|
||||
status: "published",
|
||||
availableAt: null,
|
||||
maxAttempts: 2,
|
||||
})
|
||||
mocks.assignmentTargetFindFirstMock.mockResolvedValue({ assignmentId: "a_1", studentId: "u_student" })
|
||||
mocks.countWhereMock.mockResolvedValue([{ c: 0 }])
|
||||
mocks.createIdMock.mockReturnValue("sub_1")
|
||||
|
||||
const formData = new FormData()
|
||||
formData.set("assignmentId", "a_1")
|
||||
const result = await startHomeworkSubmissionAction(null, formData)
|
||||
|
||||
expect(result).toEqual({ success: true, message: "Started", data: "sub_1" })
|
||||
expect(mocks.insertValuesMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: "sub_1",
|
||||
assignmentId: "a_1",
|
||||
studentId: "u_student",
|
||||
attemptNo: 1,
|
||||
status: "started",
|
||||
})
|
||||
)
|
||||
expect(mocks.revalidatePathMock).toHaveBeenCalledWith("/student/learning/assignments")
|
||||
})
|
||||
|
||||
it("blocks submission when assignment is past due", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_student" } })
|
||||
mocks.ensureLimitMock.mockResolvedValue([{ id: "u_student" }])
|
||||
mocks.submissionFindFirstMock.mockResolvedValue({
|
||||
id: "sub_1",
|
||||
studentId: "u_student",
|
||||
status: "started",
|
||||
assignment: {
|
||||
dueAt: new Date(Date.now() - 60_000),
|
||||
allowLate: false,
|
||||
lateDueAt: null,
|
||||
},
|
||||
})
|
||||
|
||||
const formData = new FormData()
|
||||
formData.set("submissionId", "sub_1")
|
||||
const result = await submitHomeworkAction(null, formData)
|
||||
|
||||
expect(result).toEqual({ success: false, message: "Past due" })
|
||||
})
|
||||
|
||||
it("submits started homework before due time", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_student" } })
|
||||
mocks.ensureLimitMock.mockResolvedValue([{ id: "u_student" }])
|
||||
mocks.submissionFindFirstMock.mockResolvedValue({
|
||||
id: "sub_2",
|
||||
studentId: "u_student",
|
||||
status: "started",
|
||||
assignment: {
|
||||
dueAt: new Date(Date.now() + 60_000),
|
||||
allowLate: false,
|
||||
lateDueAt: null,
|
||||
},
|
||||
})
|
||||
|
||||
const formData = new FormData()
|
||||
formData.set("submissionId", "sub_2")
|
||||
const result = await submitHomeworkAction(null, formData)
|
||||
|
||||
expect(result).toEqual({ success: true, message: "Submitted", data: "sub_2" })
|
||||
expect(mocks.updateSetMock).toHaveBeenCalledWith(expect.objectContaining({ status: "submitted", isLate: false }))
|
||||
expect(mocks.revalidatePathMock).toHaveBeenCalledWith("/teacher/homework/submissions")
|
||||
expect(mocks.revalidatePathMock).toHaveBeenCalledWith("/student/learning/assignments")
|
||||
})
|
||||
|
||||
it("blocks start when attempts are exhausted", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_student" } })
|
||||
mocks.ensureLimitMock.mockResolvedValue([{ id: "u_student" }])
|
||||
mocks.assignmentFindFirstMock.mockResolvedValue({
|
||||
id: "a_2",
|
||||
status: "published",
|
||||
availableAt: null,
|
||||
maxAttempts: 1,
|
||||
})
|
||||
mocks.assignmentTargetFindFirstMock.mockResolvedValue({ assignmentId: "a_2", studentId: "u_student" })
|
||||
mocks.countWhereMock.mockResolvedValue([{ c: 1 }])
|
||||
|
||||
const formData = new FormData()
|
||||
formData.set("assignmentId", "a_2")
|
||||
const result = await startHomeworkSubmissionAction(null, formData)
|
||||
|
||||
expect(result).toEqual({ success: false, message: "No attempts left" })
|
||||
})
|
||||
|
||||
it("grades submission and writes total score", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_teacher" } })
|
||||
mocks.ensureLimitMock.mockResolvedValue([{ id: "u_teacher", role: "teacher" }])
|
||||
|
||||
const formData = new FormData()
|
||||
formData.set("submissionId", "sub_1")
|
||||
formData.set(
|
||||
"answersJson",
|
||||
JSON.stringify([
|
||||
{ id: "ans_1", score: 5, feedback: "good" },
|
||||
{ id: "ans_2", score: 3, feedback: "" },
|
||||
])
|
||||
)
|
||||
|
||||
const result = await gradeHomeworkSubmissionAction(null, formData)
|
||||
|
||||
expect(result).toEqual({ success: true, message: "Grading saved" })
|
||||
expect(mocks.updateMock).toHaveBeenCalledTimes(3)
|
||||
expect(mocks.updateSetMock).toHaveBeenCalledWith(expect.objectContaining({ score: 8, status: "graded" }))
|
||||
expect(mocks.revalidatePathMock).toHaveBeenCalledWith("/teacher/homework/submissions")
|
||||
})
|
||||
|
||||
it("saves new answer for started submission", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_student" } })
|
||||
mocks.ensureLimitMock.mockResolvedValue([{ id: "u_student" }])
|
||||
mocks.submissionFindFirstMock.mockResolvedValue({
|
||||
id: "sub_3",
|
||||
studentId: "u_student",
|
||||
status: "started",
|
||||
assignment: {},
|
||||
})
|
||||
mocks.txAnswerFindFirstMock.mockResolvedValue(null)
|
||||
mocks.createIdMock.mockReturnValue("ans_new")
|
||||
|
||||
const formData = new FormData()
|
||||
formData.set("submissionId", "sub_3")
|
||||
formData.set("questionId", "q_3")
|
||||
formData.set("answerJson", JSON.stringify({ text: "answer content" }))
|
||||
const result = await saveHomeworkAnswerAction(null, formData)
|
||||
|
||||
expect(result).toEqual({ success: true, message: "Saved", data: "sub_3" })
|
||||
expect(mocks.txInsertValuesMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: "ans_new",
|
||||
submissionId: "sub_3",
|
||||
questionId: "q_3",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("updates existing answer for started submission", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_student" } })
|
||||
mocks.ensureLimitMock.mockResolvedValue([{ id: "u_student" }])
|
||||
mocks.submissionFindFirstMock.mockResolvedValue({
|
||||
id: "sub_4",
|
||||
studentId: "u_student",
|
||||
status: "started",
|
||||
assignment: {},
|
||||
})
|
||||
mocks.txAnswerFindFirstMock.mockResolvedValue({ id: "ans_existing" })
|
||||
|
||||
const formData = new FormData()
|
||||
formData.set("submissionId", "sub_4")
|
||||
formData.set("questionId", "q_4")
|
||||
formData.set("answerJson", JSON.stringify({ text: "updated answer" }))
|
||||
const result = await saveHomeworkAnswerAction(null, formData)
|
||||
|
||||
expect(result).toEqual({ success: true, message: "Saved", data: "sub_4" })
|
||||
expect(mocks.txUpdateMock).toHaveBeenCalledTimes(1)
|
||||
expect(mocks.txUpdateSetMock).toHaveBeenCalledWith(expect.objectContaining({ answerContent: { text: "updated answer" } }))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user