=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:
199
tests/integration/api-onboarding-complete.route.test.ts
Normal file
199
tests/integration/api-onboarding-complete.route.test.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
const whereMock = vi.fn()
|
||||
const innerJoinMock = vi.fn(() => ({ where: whereMock }))
|
||||
const fromMock = vi.fn(() => ({ innerJoin: innerJoinMock, where: whereMock }))
|
||||
const selectMock = vi.fn(() => ({ from: fromMock }))
|
||||
const updateWhereMock = vi.fn()
|
||||
const updateSetMock = vi.fn(() => ({ where: updateWhereMock }))
|
||||
const updateMock = vi.fn(() => ({ set: updateSetMock }))
|
||||
const insertOnDuplicateKeyUpdateMock = vi.fn()
|
||||
const insertValuesMock = vi.fn(() => ({ onDuplicateKeyUpdate: insertOnDuplicateKeyUpdateMock }))
|
||||
const insertMock = vi.fn(() => ({ values: insertValuesMock }))
|
||||
const enrollStudentByInvitationCodeMock = vi.fn()
|
||||
return {
|
||||
authMock: vi.fn(),
|
||||
whereMock,
|
||||
fromMock,
|
||||
selectMock,
|
||||
roleFindFirstMock: vi.fn(),
|
||||
updateWhereMock,
|
||||
updateSetMock,
|
||||
updateMock,
|
||||
insertOnDuplicateKeyUpdateMock,
|
||||
insertValuesMock,
|
||||
insertMock,
|
||||
enrollStudentByInvitationCodeMock,
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("@/auth", () => ({
|
||||
auth: mocks.authMock,
|
||||
}))
|
||||
|
||||
vi.mock("@/shared/db", () => ({
|
||||
db: {
|
||||
select: mocks.selectMock,
|
||||
query: {
|
||||
roles: {
|
||||
findFirst: mocks.roleFindFirstMock,
|
||||
},
|
||||
},
|
||||
update: mocks.updateMock,
|
||||
insert: mocks.insertMock,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("@/shared/db/schema", () => ({
|
||||
classes: { id: "id", invitationCode: "invitationCode" },
|
||||
classSubjectTeachers: { classId: "classId", subjectId: "subjectId", teacherId: "teacherId", updatedAt: "updatedAt" },
|
||||
roles: { id: "id", name: "name" },
|
||||
users: { id: "id", onboardedAt: "onboardedAt", name: "name", phone: "phone", address: "address" },
|
||||
usersToRoles: { userId: "userId", roleId: "roleId" },
|
||||
subjects: { id: "id", name: "name" },
|
||||
}))
|
||||
|
||||
vi.mock("@/modules/classes/data-access", () => ({
|
||||
enrollStudentByInvitationCode: mocks.enrollStudentByInvitationCodeMock,
|
||||
}))
|
||||
|
||||
import { POST } from "@/app/api/onboarding/complete/route"
|
||||
|
||||
describe("POST /api/onboarding/complete", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
mocks.whereMock.mockResolvedValue([])
|
||||
mocks.roleFindFirstMock.mockResolvedValue({ id: "role_student" })
|
||||
})
|
||||
|
||||
it("returns 401 when session is missing", async () => {
|
||||
mocks.authMock.mockResolvedValue(null)
|
||||
const req = new Request("http://localhost/api/onboarding/complete", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ role: "student", name: "A" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(data).toEqual({ success: false, message: "Unauthorized" })
|
||||
})
|
||||
|
||||
it("returns 400 for invalid payload structure", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_1" } })
|
||||
const req = new Request("http://localhost/api/onboarding/complete", {
|
||||
method: "POST",
|
||||
body: "not-json",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(data).toEqual({ success: false, message: "Invalid payload" })
|
||||
})
|
||||
|
||||
it("returns 400 for invalid role", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_1" } })
|
||||
const req = new Request("http://localhost/api/onboarding/complete", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ role: "guest", name: "A" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(data).toEqual({ success: false, message: "Invalid role" })
|
||||
})
|
||||
|
||||
it("returns 403 when non-admin selects admin role", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_2" } })
|
||||
mocks.whereMock.mockResolvedValue([{ name: "teacher" }])
|
||||
const req = new Request("http://localhost/api/onboarding/complete", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ role: "admin", name: "Admin User" }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(403)
|
||||
expect(data).toEqual({ success: false, message: "Forbidden" })
|
||||
})
|
||||
|
||||
it("returns 400 when name is missing", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_3" } })
|
||||
const req = new Request("http://localhost/api/onboarding/complete", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ role: "student", name: " " }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(400)
|
||||
expect(data).toEqual({ success: false, message: "Name is required" })
|
||||
})
|
||||
|
||||
it("completes student onboarding and enrolls deduplicated class codes", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_student" } })
|
||||
const req = new Request("http://localhost/api/onboarding/complete", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
role: "student",
|
||||
name: "Student A",
|
||||
classCodes: "C1, C1;C2 C3",
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(data).toEqual({ success: true })
|
||||
expect(mocks.enrollStudentByInvitationCodeMock).toHaveBeenCalledTimes(3)
|
||||
expect(mocks.enrollStudentByInvitationCodeMock).toHaveBeenNthCalledWith(1, "u_student", "C1")
|
||||
expect(mocks.enrollStudentByInvitationCodeMock).toHaveBeenNthCalledWith(2, "u_student", "C2")
|
||||
expect(mocks.enrollStudentByInvitationCodeMock).toHaveBeenNthCalledWith(3, "u_student", "C3")
|
||||
expect(mocks.updateSetMock).toHaveBeenCalled()
|
||||
expect(mocks.insertValuesMock).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("completes teacher onboarding and upserts class-subject mapping", async () => {
|
||||
mocks.authMock.mockResolvedValue({ user: { id: "u_teacher" } })
|
||||
mocks.whereMock
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce([{ id: "class_1", invitationCode: "T1" }])
|
||||
.mockResolvedValueOnce([
|
||||
{ id: "sub_math", name: "数学" },
|
||||
{ id: "sub_music", name: "音乐" },
|
||||
])
|
||||
const req = new Request("http://localhost/api/onboarding/complete", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
role: "teacher",
|
||||
name: "Teacher A",
|
||||
classCodes: "T1",
|
||||
teacherSubjects: ["数学", "音乐", "invalid"],
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
|
||||
const response = await POST(req)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(data).toEqual({ success: true })
|
||||
expect(mocks.enrollStudentByInvitationCodeMock).not.toHaveBeenCalled()
|
||||
expect(mocks.insertOnDuplicateKeyUpdateMock).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user