feat(login): 微信一键登录流程编排与单测
This commit is contained in:
parent
9871c11476
commit
44918644ef
|
|
@ -0,0 +1,24 @@
|
||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
import { wxMiniappLogin, type WxLoginResult } from '@/api/auth'
|
||||||
|
import { storage } from '@/utils/storage'
|
||||||
|
import { WX_OPENID_STORAGE_KEY, FALLBACK_WX_APP_ID } from '@/constants/auth'
|
||||||
|
|
||||||
|
// 取小程序 appId:正常环境走 accountInfo,异常(部分基座不支持)回退常量
|
||||||
|
function resolveAppId(): string {
|
||||||
|
try {
|
||||||
|
return Taro.getAccountInfoSync().miniProgram.appId || FALLBACK_WX_APP_ID
|
||||||
|
} catch {
|
||||||
|
return FALLBACK_WX_APP_ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信一键登录主流程(spec D3):
|
||||||
|
// Taro.login 换一次性 code → 组装 appId/openId → 调 miniapp/login → 缓存响应中的 openId
|
||||||
|
export async function performWxLogin(): Promise<WxLoginResult> {
|
||||||
|
const { code } = await Taro.login()
|
||||||
|
if (!code) throw new Error('获取微信登录凭证失败')
|
||||||
|
const openId = storage.get<string>(WX_OPENID_STORAGE_KEY) ?? undefined
|
||||||
|
const result = await wxMiniappLogin({ appId: resolveAppId(), code, openId })
|
||||||
|
if (result.openId) storage.set(WX_OPENID_STORAGE_KEY, result.openId)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||||
|
|
||||||
|
const mockLogin = vi.hoisted(() => vi.fn())
|
||||||
|
const mockGetAccountInfo = vi.hoisted(() => vi.fn())
|
||||||
|
vi.mock('@tarojs/taro', () => ({
|
||||||
|
default: { login: mockLogin, getAccountInfoSync: mockGetAccountInfo }
|
||||||
|
}))
|
||||||
|
|
||||||
|
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 } 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
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mem.clear()
|
||||||
|
mockLogin.mockReset()
|
||||||
|
mockGetAccountInfo.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('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 已被使用')
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue