feat: complete email binding and pending oauth verification flows

This commit is contained in:
IanShaw027
2026-04-21 10:00:06 +08:00
parent 6da08262d7
commit dcd5c43da4
29 changed files with 2117 additions and 107 deletions

View File

@@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import ProfileIdentityBindingsSection from '@/components/user/profile/ProfileIdentityBindingsSection.vue'
import { useAppStore } from '@/stores'
import { useAppStore, useAuthStore } from '@/stores'
import type { User } from '@/types'
const routeState = vi.hoisted(() => ({
@@ -15,10 +15,24 @@ const locationState = vi.hoisted(() => ({
let pinia: ReturnType<typeof createPinia>
const userApiMocks = vi.hoisted(() => ({
sendEmailBindingCode: vi.fn(),
bindEmailIdentity: vi.fn(),
}))
vi.mock('vue-router', () => ({
useRoute: () => routeState,
}))
vi.mock('@/api/user', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/api/user')>()
return {
...actual,
sendEmailBindingCode: (...args: any[]) => userApiMocks.sendEmailBindingCode(...args),
bindEmailIdentity: (...args: any[]) => userApiMocks.bindEmailIdentity(...args),
}
})
vi.mock('vue-i18n', async (importOriginal) => {
const actual = await importOriginal<typeof import('vue-i18n')>()
return {
@@ -34,6 +48,13 @@ vi.mock('vue-i18n', async (importOriginal) => {
if (key === 'profile.authBindings.providers.wechat') return 'WeChat'
if (key === 'profile.authBindings.providers.oidc') return params?.providerName || 'OIDC'
if (key === 'profile.authBindings.bindAction') return `Bind ${params?.providerName || ''}`.trim()
if (key === 'profile.authBindings.emailPlaceholder') return 'Email address'
if (key === 'profile.authBindings.codePlaceholder') return 'Verification code'
if (key === 'profile.authBindings.passwordPlaceholder') return 'Set password'
if (key === 'profile.authBindings.sendCodeAction') return 'Send code'
if (key === 'profile.authBindings.confirmEmailBindAction') return 'Bind email'
if (key === 'profile.authBindings.codeSentTo') return `Code sent to ${params?.email || ''}`.trim()
if (key === 'profile.authBindings.bindSuccess') return 'Bind success'
return key
},
}),
@@ -76,6 +97,8 @@ describe('ProfileIdentityBindingsSection', () => {
const appStore = useAppStore()
appStore.cachedPublicSettings = null
appStore.publicSettingsLoaded = false
userApiMocks.sendEmailBindingCode.mockReset()
userApiMocks.bindEmailIdentity.mockReset()
})
afterEach(() => {
@@ -224,4 +247,58 @@ describe('ProfileIdentityBindingsSection', () => {
expect(wrapper.find('[data-testid="profile-binding-wechat-action"]').exists()).toBe(true)
})
it('sends email verification code and binds email from the profile card', async () => {
userApiMocks.sendEmailBindingCode.mockResolvedValue(undefined)
userApiMocks.bindEmailIdentity.mockResolvedValue(
createUser({
email: 'bound@example.com',
email_bound: true,
auth_bindings: {
email: { bound: true },
},
})
)
const appStore = useAppStore()
const authStore = useAuthStore()
authStore.user = createUser({
email: 'legacy-user@linuxdo-connect.invalid',
email_bound: false,
auth_bindings: {
email: { bound: false },
},
})
const showSuccessSpy = vi.spyOn(appStore, 'showSuccess')
const wrapper = mount(ProfileIdentityBindingsSection, {
global: {
plugins: [pinia],
},
props: {
user: authStore.user,
linuxdoEnabled: false,
oidcEnabled: false,
wechatEnabled: false,
},
})
await wrapper.get('[data-testid="profile-binding-email-input"]').setValue('bound@example.com')
await wrapper.get('[data-testid="profile-binding-email-send-code"]').trigger('click')
expect(userApiMocks.sendEmailBindingCode).toHaveBeenCalledWith('bound@example.com')
expect(showSuccessSpy).toHaveBeenCalledWith('Code sent to bound@example.com')
await wrapper.get('[data-testid="profile-binding-email-code-input"]').setValue('123456')
await wrapper.get('[data-testid="profile-binding-email-password-input"]').setValue('new-password')
await wrapper.get('[data-testid="profile-binding-email-submit"]').trigger('click')
expect(userApiMocks.bindEmailIdentity).toHaveBeenCalledWith({
email: 'bound@example.com',
verify_code: '123456',
password: 'new-password',
})
expect(wrapper.get('[data-testid="profile-binding-email-status"]').text()).toBe('Bound')
expect(authStore.user?.email).toBe('bound@example.com')
})
})