SEC_Warehouse/tests/unit/login/wxLoginFlow.spec.ts

189 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect, beforeEach, vi } from 'vitest'
const mockLogin = vi.hoisted(() => vi.fn())
const mockGetAccountInfo = vi.hoisted(() => vi.fn())
const mockGetUserInfo = vi.hoisted(() => vi.fn())
vi.mock('@tarojs/taro', () => ({
default: {
login: mockLogin,
getAccountInfoSync: mockGetAccountInfo,
getUserInfo: mockGetUserInfo
}
}))
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()
mockGetUserInfo.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('getUserInfo(withCredentials) → 换新 code → 带 iv/encryptedData/openId 重调登录', async () => {
mockGetUserInfo.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(mockGetUserInfo).toHaveBeenCalledWith({ withCredentials: true })
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 () => {
mockGetUserInfo.mockRejectedValue({ errMsg: 'getUserInfo:fail auth deny' })
await expect(completeUserInfoLogin('o-need')).rejects.toThrow('getUserInfo:fail auth deny')
expect(mockWxMiniappLogin).not.toHaveBeenCalled()
})
it('授权成功但换 code 失败:抛错且不发起登录请求', async () => {
mockGetUserInfo.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()
})
})