173 lines
4.8 KiB
TypeScript
173 lines
4.8 KiB
TypeScript
import { NextRequest } from 'next/server'
|
|
import { POST } from '../route'
|
|
|
|
jest.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
user: {
|
|
findUnique: jest.fn(),
|
|
update: jest.fn(),
|
|
create: jest.fn(),
|
|
},
|
|
dingtalkLoginToken: {
|
|
create: jest.fn(),
|
|
},
|
|
},
|
|
}))
|
|
|
|
jest.mock('bcryptjs', () => ({
|
|
hash: jest.fn().mockResolvedValue('hashed-random-password'),
|
|
}))
|
|
|
|
const { prisma } = require('@/lib/prisma')
|
|
const bcrypt = require('bcryptjs')
|
|
|
|
function mockDingtalkFetch() {
|
|
;(global.fetch as jest.Mock)
|
|
.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({ accessToken: 'user-access-token' }),
|
|
})
|
|
.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({ errcode: 0, access_token: 'app-access-token' }),
|
|
})
|
|
.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({ unionId: 'union-1' }),
|
|
})
|
|
.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({ errcode: 0, result: { userid: 'dingtalk-user-1' } }),
|
|
})
|
|
.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({
|
|
errcode: 0,
|
|
result: {
|
|
name: '张三',
|
|
email: 'zhangsan@example.com',
|
|
mobile: '13800000000',
|
|
avatar: 'https://example.com/avatar.png',
|
|
},
|
|
}),
|
|
})
|
|
}
|
|
|
|
describe('POST /api/dingtalk/login', () => {
|
|
const originalEnv = process.env
|
|
|
|
beforeEach(() => {
|
|
jest.resetAllMocks()
|
|
bcrypt.hash.mockResolvedValue('hashed-random-password')
|
|
process.env = {
|
|
...originalEnv,
|
|
DINGTALK_CLIENT_ID: 'client-id',
|
|
DINGTALK_CLIENT_SECRET: 'client-secret',
|
|
}
|
|
global.fetch = jest.fn()
|
|
})
|
|
|
|
afterAll(() => {
|
|
process.env = originalEnv
|
|
})
|
|
|
|
it('缺少钉钉配置时直接拒绝,不请求外部接口', async () => {
|
|
delete process.env.DINGTALK_CLIENT_ID
|
|
delete process.env.DINGTALK_CLIENT_SECRET
|
|
|
|
const response = await POST(
|
|
new NextRequest('http://localhost/api/dingtalk/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ code: 'auth-code' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(503)
|
|
expect(data.message).toContain('未配置钉钉登录')
|
|
expect(global.fetch).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('绑定同名老用户时不覆盖原密码,并返回一次性登录票据', async () => {
|
|
mockDingtalkFetch()
|
|
const existingUser = {
|
|
id: 'user-1',
|
|
email: 'old@example.com',
|
|
username: '张三',
|
|
password: 'old-password-hash',
|
|
dingtalkUserId: null,
|
|
realName: null,
|
|
avatar: null,
|
|
phone: null,
|
|
department: '设计部',
|
|
role: 'USER',
|
|
isActive: true,
|
|
}
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce(null)
|
|
.mockResolvedValueOnce(existingUser)
|
|
prisma.user.update.mockResolvedValue({
|
|
...existingUser,
|
|
dingtalkUserId: 'dingtalk-user-1',
|
|
loginType: 'dingtalk',
|
|
realName: '张三',
|
|
})
|
|
prisma.dingtalkLoginToken.create.mockResolvedValue({ id: 'token-row' })
|
|
|
|
const response = await POST(
|
|
new NextRequest('http://localhost/api/dingtalk/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ code: 'auth-code' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.loginToken).toEqual(expect.any(String))
|
|
expect(prisma.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-1' },
|
|
data: expect.not.objectContaining({ password: expect.anything() }),
|
|
})
|
|
expect(bcrypt.hash).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('创建钉钉新用户时使用随机不可知密码,不写固定密码', async () => {
|
|
mockDingtalkFetch()
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce(null)
|
|
.mockResolvedValueOnce(null)
|
|
.mockResolvedValueOnce(null)
|
|
prisma.user.create.mockResolvedValue({
|
|
id: 'user-2',
|
|
email: 'zhangsan@example.com',
|
|
username: '张三',
|
|
realName: '张三',
|
|
avatar: 'https://example.com/avatar.png',
|
|
role: 'USER',
|
|
department: '',
|
|
isActive: false,
|
|
})
|
|
prisma.dingtalkLoginToken.create.mockResolvedValue({ id: 'profile-token-row' })
|
|
|
|
const response = await POST(
|
|
new NextRequest('http://localhost/api/dingtalk/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ code: 'auth-code' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.needCompleteProfile).toBe(true)
|
|
expect(data.profileToken).toEqual(expect.any(String))
|
|
expect(bcrypt.hash).toHaveBeenCalledWith(expect.not.stringContaining('dingtalk_auto_login'), 10)
|
|
expect(prisma.user.create).toHaveBeenCalledWith({
|
|
data: expect.objectContaining({
|
|
password: 'hashed-random-password',
|
|
loginType: 'dingtalk',
|
|
dingtalkUserId: 'dingtalk-user-1',
|
|
}),
|
|
})
|
|
})
|
|
})
|