188 lines
6.9 KiB
TypeScript
188 lines
6.9 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||
|
||
const mockLogin = vi.hoisted(() => vi.fn())
|
||
const mockGetAccountInfo = vi.hoisted(() => vi.fn())
|
||
const mockGetUserProfile = vi.hoisted(() => vi.fn())
|
||
vi.mock('@tarojs/taro', () => ({
|
||
default: {
|
||
login: mockLogin,
|
||
getAccountInfoSync: mockGetAccountInfo,
|
||
getUserProfile: mockGetUserProfile
|
||
}
|
||
}))
|
||
|
||
const mockWxMiniappLogin = vi.hoisted(() => vi.fn())
|
||
vi.mock('@/api/auth', () => ({ wxMiniappLogin: mockWxMiniappLogin }))
|
||
|
||
const mem = new Map<string, unknown>()
|
||
vi.mock('@/utils/storage', () => ({
|
||
storage: {
|
||
get: <T>(key: string): T | null => (mem.has(key) ? (mem.get(key) as T) : null),
|
||
set: <T>(key: string, value: T): void => { mem.set(key, value) },
|
||
remove: (key: string): void => { mem.delete(key) }
|
||
}
|
||
}))
|
||
|
||
import { performWxLogin, completeUserInfoLogin, completeMobileLogin, getLoginSubState } from '@/pages/login/wxLoginFlow'
|
||
|
||
const result = (over: Record<string, unknown> = {}) => ({
|
||
userInfo: {
|
||
userId: 'u-1', userType: 1, avatar: '', nickName: '张三', mobile: '',
|
||
gender: 1, region: '', loginFrom: 'miniapp'
|
||
},
|
||
code: 'tk-abc',
|
||
...over
|
||
})
|
||
|
||
// 首次登录 needUserInfo 响应形态(http 层把 Message 附加到 message 字段)
|
||
const needInfoResult = () => ({
|
||
openId: 'o-need',
|
||
bindKey: 'bind-1',
|
||
message: 'needUserInfo' as const
|
||
})
|
||
|
||
// noMobile 响应形态(授权头像后仍未绑定手机号)
|
||
const noMobileResult = () => ({
|
||
bindKey: 'bind-2',
|
||
message: 'noMobile' as const
|
||
})
|
||
|
||
beforeEach(() => {
|
||
mem.clear()
|
||
mockLogin.mockReset()
|
||
mockGetAccountInfo.mockReset()
|
||
mockGetUserProfile.mockReset()
|
||
mockWxMiniappLogin.mockReset()
|
||
mockLogin.mockResolvedValue({ code: 'wx-code-1' })
|
||
mockGetAccountInfo.mockReturnValue({ miniProgram: { appId: 'wx-appid-live' } })
|
||
})
|
||
|
||
describe('performWxLogin', () => {
|
||
it('完整流程:Taro.login → appId/openId 组装 → 返回登录结果', async () => {
|
||
mockWxMiniappLogin.mockResolvedValue(result())
|
||
const res = await performWxLogin()
|
||
expect(res.code).toBe('tk-abc')
|
||
expect(mockWxMiniappLogin).toHaveBeenCalledWith({
|
||
appId: 'wx-appid-live',
|
||
code: 'wx-code-1',
|
||
openId: undefined
|
||
})
|
||
})
|
||
|
||
it('首次登录(本地无 openId)请求不带 openId,结果含 openId 时写入缓存', async () => {
|
||
mockWxMiniappLogin.mockResolvedValue(result({ openId: 'o-abc' }))
|
||
await performWxLogin()
|
||
expect(mockWxMiniappLogin.mock.calls[0][0].openId).toBeUndefined()
|
||
expect(mem.get('wx_openid')).toBe('o-abc')
|
||
})
|
||
|
||
it('二次登录:本地已有 openId 则随请求回传', async () => {
|
||
mem.set('wx_openid', 'o-saved')
|
||
mockWxMiniappLogin.mockResolvedValue(result())
|
||
await performWxLogin()
|
||
expect(mockWxMiniappLogin.mock.calls[0][0].openId).toBe('o-saved')
|
||
})
|
||
|
||
it('结果不含 openId 时不覆盖本地缓存', async () => {
|
||
mem.set('wx_openid', 'o-saved')
|
||
mockWxMiniappLogin.mockResolvedValue(result())
|
||
await performWxLogin()
|
||
expect(mem.get('wx_openid')).toBe('o-saved')
|
||
})
|
||
|
||
it('Taro.login 返回空 code 抛错(不发起登录请求)', async () => {
|
||
mockLogin.mockResolvedValue({ code: '' })
|
||
await expect(performWxLogin()).rejects.toThrow('获取微信登录凭证失败')
|
||
expect(mockWxMiniappLogin).not.toHaveBeenCalled()
|
||
})
|
||
|
||
it('Taro.login reject(errMsg 形状)时抛带 message 的 Error', async () => {
|
||
mockLogin.mockRejectedValue({ errMsg: 'login:fail:cancel' })
|
||
await expect(performWxLogin()).rejects.toThrow('微信登录失败')
|
||
expect(mockWxMiniappLogin).not.toHaveBeenCalled()
|
||
})
|
||
|
||
it('getAccountInfoSync 抛错时回退常量 appId', async () => {
|
||
mockGetAccountInfo.mockImplementation(() => { throw new Error('not supported') })
|
||
mockWxMiniappLogin.mockResolvedValue(result())
|
||
await performWxLogin()
|
||
expect(mockWxMiniappLogin.mock.calls[0][0].appId).toBe('wxe9773bb6f025ab00')
|
||
})
|
||
|
||
it('接口业务错(BusinessError)原样透传', async () => {
|
||
const err = new Error('code 已被使用')
|
||
err.name = 'BusinessError'
|
||
mockWxMiniappLogin.mockRejectedValue(err)
|
||
await expect(performWxLogin()).rejects.toThrow('code 已被使用')
|
||
})
|
||
})
|
||
|
||
describe('needUserInfo 首次登录分支', () => {
|
||
it('performWxLogin 返回 needUserInfo 形态时缓存 openId 并原样返回', async () => {
|
||
mockWxMiniappLogin.mockResolvedValue(needInfoResult())
|
||
const res = await performWxLogin()
|
||
expect(getLoginSubState(res)).toBe('needUserInfo')
|
||
expect(res.openId).toBe('o-need')
|
||
expect(mem.get('wx_openid')).toBe('o-need')
|
||
})
|
||
|
||
it('getLoginSubState 对完整登录响应返回 complete', () => {
|
||
expect(getLoginSubState(result())).toBe('complete')
|
||
})
|
||
|
||
it('getLoginSubState 对 noMobile 响应返回 noMobile', () => {
|
||
expect(getLoginSubState(noMobileResult() as never)).toBe('noMobile')
|
||
})
|
||
})
|
||
|
||
describe('completeUserInfoLogin 二次授权', () => {
|
||
it('getUserProfile → 换新 code → 带 iv/encryptedData/openId 重调登录', async () => {
|
||
mockGetUserProfile.mockResolvedValue({ iv: 'iv-1', encryptedData: 'enc-1' })
|
||
mockWxMiniappLogin.mockResolvedValue(result({ openId: 'o-need' }))
|
||
const res = await completeUserInfoLogin('o-need')
|
||
expect(res.code).toBe('tk-abc')
|
||
expect(mockWxMiniappLogin).toHaveBeenCalledWith({
|
||
appId: 'wx-appid-live',
|
||
code: 'wx-code-1',
|
||
openId: 'o-need',
|
||
iv: 'iv-1',
|
||
encryptedData: 'enc-1'
|
||
})
|
||
})
|
||
|
||
it('用户拒绝授权(errMsg 形状 reject)抛带 message 的 Error', async () => {
|
||
mockGetUserProfile.mockRejectedValue({ errMsg: 'getUserProfile:fail auth deny' })
|
||
await expect(completeUserInfoLogin('o-need')).rejects.toThrow('getUserProfile:fail auth deny')
|
||
expect(mockWxMiniappLogin).not.toHaveBeenCalled()
|
||
})
|
||
|
||
it('授权成功但换 code 失败:抛错且不发起登录请求', async () => {
|
||
mockGetUserProfile.mockResolvedValue({ iv: 'iv-1', encryptedData: 'enc-1' })
|
||
mockLogin.mockRejectedValue({ errMsg: 'login:fail' })
|
||
await expect(completeUserInfoLogin('o-need')).rejects.toThrow('获取微信登录凭证失败,请重试')
|
||
expect(mockWxMiniappLogin).not.toHaveBeenCalled()
|
||
})
|
||
})
|
||
|
||
describe('completeMobileLogin 手机号授权', () => {
|
||
it('按钮回调 detail 带 iv/encryptedData → 重调登录接口补手机号', async () => {
|
||
mockWxMiniappLogin.mockResolvedValue(result())
|
||
const res = await completeMobileLogin('o-need', { iv: 'iv-ph', encryptedData: 'enc-ph' })
|
||
expect(res.code).toBe('tk-abc')
|
||
expect(mockWxMiniappLogin).toHaveBeenCalledWith({
|
||
appId: 'wx-appid-live',
|
||
code: 'wx-code-1',
|
||
openId: 'o-need',
|
||
iv: 'iv-ph',
|
||
encryptedData: 'enc-ph'
|
||
})
|
||
})
|
||
|
||
it('用户拒绝手机号授权(detail 无加密数据):抛 errMsg 且不发起请求', async () => {
|
||
await expect(
|
||
completeMobileLogin('o-need', { iv: '', encryptedData: '', errMsg: 'getPhoneNumber:fail user deny' })
|
||
).rejects.toThrow('getPhoneNumber:fail user deny')
|
||
expect(mockWxMiniappLogin).not.toHaveBeenCalled()
|
||
})
|
||
})
|