mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-05-05 13:40:44 +08:00
feat: complete pending oauth account creation UI
This commit is contained in:
@@ -113,37 +113,14 @@
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
Enter an email address to create your account and continue.
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
<input
|
||||
v-model="pendingAccountEmail"
|
||||
data-testid="linuxdo-create-account-email"
|
||||
type="email"
|
||||
class="input w-full"
|
||||
placeholder="you@example.com"
|
||||
:disabled="isSubmitting"
|
||||
@keyup.enter="handleCreateAccount"
|
||||
/>
|
||||
<button
|
||||
data-testid="linuxdo-create-account-submit"
|
||||
class="btn btn-primary w-full"
|
||||
:disabled="isSubmitting || !pendingAccountEmail.trim()"
|
||||
@click="handleCreateAccount"
|
||||
>
|
||||
{{ isSubmitting ? t('common.processing') : 'Create account' }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary w-full"
|
||||
:disabled="isSubmitting"
|
||||
@click="switchToBindLoginMode"
|
||||
>
|
||||
I already have an account
|
||||
</button>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<p v-if="accountActionError" class="text-sm text-red-600 dark:text-red-400">
|
||||
{{ accountActionError }}
|
||||
</p>
|
||||
</transition>
|
||||
<PendingOAuthCreateAccountForm
|
||||
test-id-prefix="linuxdo"
|
||||
:initial-email="pendingAccountEmail"
|
||||
:is-submitting="isSubmitting"
|
||||
:error-message="accountActionError"
|
||||
@submit="handleCreateAccount"
|
||||
@switch-to-bind="switchToBindLoginMode"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="needsBindLogin">
|
||||
@@ -258,6 +235,9 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { AuthLayout } from '@/components/layout'
|
||||
import PendingOAuthCreateAccountForm, {
|
||||
type PendingOAuthCreateAccountPayload
|
||||
} from '@/components/auth/PendingOAuthCreateAccountForm.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import { apiClient } from '@/api/client'
|
||||
import { useAuthStore, useAppStore } from '@/stores'
|
||||
@@ -432,9 +412,9 @@ function applyTotpChallenge(completion: LinuxDoPendingActionResponse): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
function switchToBindLoginMode() {
|
||||
function switchToBindLoginMode(nextEmail?: string) {
|
||||
pendingAccountAction.value = 'bind_login'
|
||||
bindLoginEmail.value = bindLoginEmail.value.trim() || pendingAccountEmail.value.trim()
|
||||
bindLoginEmail.value = bindLoginEmail.value.trim() || nextEmail?.trim() || pendingAccountEmail.value.trim()
|
||||
bindLoginPassword.value = ''
|
||||
accountActionError.value = ''
|
||||
canReturnToCreateAccount.value = true
|
||||
@@ -533,15 +513,16 @@ async function handleContinueLogin() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateAccount() {
|
||||
async function handleCreateAccount(payload: PendingOAuthCreateAccountPayload) {
|
||||
accountActionError.value = ''
|
||||
const email = pendingAccountEmail.value.trim()
|
||||
if (!email) return
|
||||
if (!payload.email || !payload.password) return
|
||||
|
||||
isSubmitting.value = true
|
||||
try {
|
||||
const { data } = await apiClient.post<LinuxDoPendingActionResponse>('/auth/oauth/pending/create-account', {
|
||||
email,
|
||||
email: payload.email,
|
||||
password: payload.password,
|
||||
verify_code: payload.verifyCode || undefined,
|
||||
...serializeAdoptionDecision(currentAdoptionDecision())
|
||||
})
|
||||
await finalizePendingAccountResponse(data)
|
||||
|
||||
@@ -122,37 +122,14 @@
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
Enter an email address to create your account and continue.
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
<input
|
||||
v-model="pendingAccountEmail"
|
||||
data-testid="oidc-create-account-email"
|
||||
type="email"
|
||||
class="input w-full"
|
||||
placeholder="you@example.com"
|
||||
:disabled="isSubmitting"
|
||||
@keyup.enter="handleCreateAccount"
|
||||
/>
|
||||
<button
|
||||
data-testid="oidc-create-account-submit"
|
||||
class="btn btn-primary w-full"
|
||||
:disabled="isSubmitting || !pendingAccountEmail.trim()"
|
||||
@click="handleCreateAccount"
|
||||
>
|
||||
{{ isSubmitting ? t('common.processing') : 'Create account' }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary w-full"
|
||||
:disabled="isSubmitting"
|
||||
@click="switchToBindLoginMode"
|
||||
>
|
||||
I already have an account
|
||||
</button>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<p v-if="accountActionError" class="text-sm text-red-600 dark:text-red-400">
|
||||
{{ accountActionError }}
|
||||
</p>
|
||||
</transition>
|
||||
<PendingOAuthCreateAccountForm
|
||||
test-id-prefix="oidc"
|
||||
:initial-email="pendingAccountEmail"
|
||||
:is-submitting="isSubmitting"
|
||||
:error-message="accountActionError"
|
||||
@submit="handleCreateAccount"
|
||||
@switch-to-bind="switchToBindLoginMode"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="needsBindLogin">
|
||||
@@ -267,6 +244,9 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { AuthLayout } from '@/components/layout'
|
||||
import PendingOAuthCreateAccountForm, {
|
||||
type PendingOAuthCreateAccountPayload
|
||||
} from '@/components/auth/PendingOAuthCreateAccountForm.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import { apiClient } from '@/api/client'
|
||||
import { useAuthStore, useAppStore } from '@/stores'
|
||||
@@ -476,9 +456,9 @@ function applyTotpChallenge(completion: PendingOidcCompletion): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
function switchToBindLoginMode() {
|
||||
function switchToBindLoginMode(nextEmail?: string) {
|
||||
pendingAccountAction.value = 'bind_login'
|
||||
bindLoginEmail.value = bindLoginEmail.value.trim() || pendingAccountEmail.value.trim()
|
||||
bindLoginEmail.value = bindLoginEmail.value.trim() || nextEmail?.trim() || pendingAccountEmail.value.trim()
|
||||
bindLoginPassword.value = ''
|
||||
accountActionError.value = ''
|
||||
canReturnToCreateAccount.value = true
|
||||
@@ -577,15 +557,16 @@ async function handleContinueLogin() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateAccount() {
|
||||
async function handleCreateAccount(payload: PendingOAuthCreateAccountPayload) {
|
||||
accountActionError.value = ''
|
||||
const email = pendingAccountEmail.value.trim()
|
||||
if (!email) return
|
||||
if (!payload.email || !payload.password) return
|
||||
|
||||
isSubmitting.value = true
|
||||
try {
|
||||
const { data } = await apiClient.post<PendingOidcCompletion>('/auth/oauth/pending/create-account', {
|
||||
email,
|
||||
email: payload.email,
|
||||
password: payload.password,
|
||||
verify_code: payload.verifyCode || undefined,
|
||||
...serializeAdoptionDecision(currentAdoptionDecision())
|
||||
})
|
||||
await finalizePendingAccountResponse(data)
|
||||
|
||||
@@ -160,37 +160,14 @@
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
Enter an email address to create your account and continue.
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
<input
|
||||
v-model="pendingAccountEmail"
|
||||
data-testid="wechat-create-account-email"
|
||||
type="email"
|
||||
class="input w-full"
|
||||
placeholder="you@example.com"
|
||||
:disabled="isSubmitting"
|
||||
@keyup.enter="handleCreateAccount"
|
||||
/>
|
||||
<button
|
||||
data-testid="wechat-create-account-submit"
|
||||
class="btn btn-primary w-full"
|
||||
:disabled="isSubmitting || !pendingAccountEmail.trim()"
|
||||
@click="handleCreateAccount"
|
||||
>
|
||||
{{ isSubmitting ? t('common.processing') : 'Create account' }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary w-full"
|
||||
:disabled="isSubmitting"
|
||||
@click="switchToBindLoginMode"
|
||||
>
|
||||
I already have an account
|
||||
</button>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<p v-if="accountActionError" class="text-sm text-red-600 dark:text-red-400">
|
||||
{{ accountActionError }}
|
||||
</p>
|
||||
</transition>
|
||||
<PendingOAuthCreateAccountForm
|
||||
test-id-prefix="wechat"
|
||||
:initial-email="pendingAccountEmail"
|
||||
:is-submitting="isSubmitting"
|
||||
:error-message="accountActionError"
|
||||
@submit="handleCreateAccount"
|
||||
@switch-to-bind="switchToBindLoginMode"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="needsBindLogin">
|
||||
@@ -305,6 +282,9 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { AuthLayout } from '@/components/layout'
|
||||
import PendingOAuthCreateAccountForm, {
|
||||
type PendingOAuthCreateAccountPayload
|
||||
} from '@/components/auth/PendingOAuthCreateAccountForm.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import { apiClient } from '@/api/client'
|
||||
import { useAuthStore, useAppStore } from '@/stores'
|
||||
@@ -575,9 +555,9 @@ function applyTotpChallenge(completion: PendingWeChatCompletion): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
function switchToBindLoginMode() {
|
||||
function switchToBindLoginMode(nextEmail?: string) {
|
||||
pendingAccountAction.value = 'bind_login'
|
||||
bindLoginEmail.value = bindLoginEmail.value.trim() || pendingAccountEmail.value.trim()
|
||||
bindLoginEmail.value = bindLoginEmail.value.trim() || nextEmail?.trim() || pendingAccountEmail.value.trim()
|
||||
bindLoginPassword.value = ''
|
||||
accountActionError.value = ''
|
||||
canReturnToCreateAccount.value = true
|
||||
@@ -676,15 +656,16 @@ async function handleContinueLogin() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateAccount() {
|
||||
async function handleCreateAccount(payload: PendingOAuthCreateAccountPayload) {
|
||||
accountActionError.value = ''
|
||||
const email = pendingAccountEmail.value.trim()
|
||||
if (!email) return
|
||||
if (!payload.email || !payload.password) return
|
||||
|
||||
isSubmitting.value = true
|
||||
try {
|
||||
const { data } = await apiClient.post<PendingWeChatCompletion>('/auth/oauth/pending/create-account', {
|
||||
email,
|
||||
email: payload.email,
|
||||
password: payload.password,
|
||||
verify_code: payload.verifyCode || undefined,
|
||||
...serializeAdoptionDecision(currentAdoptionDecision())
|
||||
})
|
||||
await finalizePendingAccountResponse(data)
|
||||
|
||||
@@ -11,6 +11,7 @@ const exchangePendingOAuthCompletion = vi.fn()
|
||||
const completeLinuxDoOAuthRegistration = vi.fn()
|
||||
const login2FA = vi.fn()
|
||||
const apiClientPost = vi.fn()
|
||||
const sendVerifyCode = vi.fn()
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => ({
|
||||
@@ -53,7 +54,8 @@ vi.mock('@/api/auth', async () => {
|
||||
...actual,
|
||||
exchangePendingOAuthCompletion: (...args: any[]) => exchangePendingOAuthCompletion(...args),
|
||||
completeLinuxDoOAuthRegistration: (...args: any[]) => completeLinuxDoOAuthRegistration(...args),
|
||||
login2FA: (...args: any[]) => login2FA(...args)
|
||||
login2FA: (...args: any[]) => login2FA(...args),
|
||||
sendVerifyCode: (...args: any[]) => sendVerifyCode(...args)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -67,6 +69,7 @@ describe('LinuxDoCallbackView', () => {
|
||||
completeLinuxDoOAuthRegistration.mockReset()
|
||||
login2FA.mockReset()
|
||||
apiClientPost.mockReset()
|
||||
sendVerifyCode.mockReset()
|
||||
})
|
||||
|
||||
it('does not send adoption decisions during the initial exchange', async () => {
|
||||
@@ -251,7 +254,7 @@ describe('LinuxDoCallbackView', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('collects email for pending oauth account creation and submits adoption decisions', async () => {
|
||||
it('collects email, password, and verify code for pending oauth account creation and submits adoption decisions', async () => {
|
||||
exchangePendingOAuthCompletion.mockResolvedValue({
|
||||
error: 'email_required',
|
||||
redirect: '/welcome',
|
||||
@@ -286,11 +289,15 @@ describe('LinuxDoCallbackView', () => {
|
||||
expect(checkboxes).toHaveLength(2)
|
||||
await checkboxes[1].setValue(false)
|
||||
await wrapper.get('[data-testid="linuxdo-create-account-email"]').setValue(' new@example.com ')
|
||||
await wrapper.get('[data-testid="linuxdo-create-account-password"]').setValue('secret-123')
|
||||
await wrapper.get('[data-testid="linuxdo-create-account-verify-code"]').setValue('246810')
|
||||
await wrapper.get('[data-testid="linuxdo-create-account-submit"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(apiClientPost).toHaveBeenCalledWith('/auth/oauth/pending/create-account', {
|
||||
email: 'new@example.com',
|
||||
password: 'secret-123',
|
||||
verify_code: '246810',
|
||||
adopt_display_name: true,
|
||||
adopt_avatar: false
|
||||
})
|
||||
@@ -298,6 +305,38 @@ describe('LinuxDoCallbackView', () => {
|
||||
expect(replace).toHaveBeenCalledWith('/welcome')
|
||||
})
|
||||
|
||||
it('sends a verify code for pending oauth account creation', async () => {
|
||||
exchangePendingOAuthCompletion.mockResolvedValue({
|
||||
error: 'email_required',
|
||||
redirect: '/welcome'
|
||||
})
|
||||
sendVerifyCode.mockResolvedValue({
|
||||
message: 'sent',
|
||||
countdown: 60
|
||||
})
|
||||
|
||||
const wrapper = mount(LinuxDoCallbackView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AuthLayout: { template: '<div><slot /></div>' },
|
||||
Icon: true,
|
||||
RouterLink: { template: '<a><slot /></a>' },
|
||||
transition: false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.get('[data-testid="linuxdo-create-account-email"]').setValue(' new@example.com ')
|
||||
await wrapper.get('[data-testid="linuxdo-create-account-send-code"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(sendVerifyCode).toHaveBeenCalledWith({
|
||||
email: 'new@example.com'
|
||||
})
|
||||
})
|
||||
|
||||
it('shows bind-login form for existing account binding and submits credentials with adoption decisions', async () => {
|
||||
exchangePendingOAuthCompletion.mockResolvedValue({
|
||||
error: 'bind_login_required',
|
||||
|
||||
@@ -12,6 +12,7 @@ const completeOIDCOAuthRegistration = vi.fn()
|
||||
const getPublicSettings = vi.fn()
|
||||
const login2FA = vi.fn()
|
||||
const apiClientPost = vi.fn()
|
||||
const sendVerifyCode = vi.fn()
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => ({
|
||||
@@ -60,7 +61,8 @@ vi.mock('@/api/auth', async () => {
|
||||
exchangePendingOAuthCompletion: (...args: any[]) => exchangePendingOAuthCompletion(...args),
|
||||
completeOIDCOAuthRegistration: (...args: any[]) => completeOIDCOAuthRegistration(...args),
|
||||
getPublicSettings: (...args: any[]) => getPublicSettings(...args),
|
||||
login2FA: (...args: any[]) => login2FA(...args)
|
||||
login2FA: (...args: any[]) => login2FA(...args),
|
||||
sendVerifyCode: (...args: any[]) => sendVerifyCode(...args)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -75,6 +77,7 @@ describe('OidcCallbackView', () => {
|
||||
getPublicSettings.mockReset()
|
||||
login2FA.mockReset()
|
||||
apiClientPost.mockReset()
|
||||
sendVerifyCode.mockReset()
|
||||
getPublicSettings.mockResolvedValue({
|
||||
oidc_oauth_provider_name: 'ExampleID'
|
||||
})
|
||||
@@ -234,7 +237,7 @@ describe('OidcCallbackView', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('collects email for pending oauth account creation and submits adoption decisions', async () => {
|
||||
it('collects email, password, and verify code for pending oauth account creation and submits adoption decisions', async () => {
|
||||
exchangePendingOAuthCompletion.mockResolvedValue({
|
||||
error: 'email_required',
|
||||
redirect: '/welcome',
|
||||
@@ -269,11 +272,15 @@ describe('OidcCallbackView', () => {
|
||||
expect(checkboxes).toHaveLength(2)
|
||||
await checkboxes[1].setValue(false)
|
||||
await wrapper.get('[data-testid="oidc-create-account-email"]').setValue(' new@example.com ')
|
||||
await wrapper.get('[data-testid="oidc-create-account-password"]').setValue('secret-123')
|
||||
await wrapper.get('[data-testid="oidc-create-account-verify-code"]').setValue('246810')
|
||||
await wrapper.get('[data-testid="oidc-create-account-submit"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(apiClientPost).toHaveBeenCalledWith('/auth/oauth/pending/create-account', {
|
||||
email: 'new@example.com',
|
||||
password: 'secret-123',
|
||||
verify_code: '246810',
|
||||
adopt_display_name: true,
|
||||
adopt_avatar: false
|
||||
})
|
||||
@@ -281,6 +288,38 @@ describe('OidcCallbackView', () => {
|
||||
expect(replace).toHaveBeenCalledWith('/welcome')
|
||||
})
|
||||
|
||||
it('sends a verify code for pending oauth account creation', async () => {
|
||||
exchangePendingOAuthCompletion.mockResolvedValue({
|
||||
error: 'email_required',
|
||||
redirect: '/welcome'
|
||||
})
|
||||
sendVerifyCode.mockResolvedValue({
|
||||
message: 'sent',
|
||||
countdown: 60
|
||||
})
|
||||
|
||||
const wrapper = mount(OidcCallbackView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AuthLayout: { template: '<div><slot /></div>' },
|
||||
Icon: true,
|
||||
RouterLink: { template: '<a><slot /></a>' },
|
||||
transition: false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.get('[data-testid="oidc-create-account-email"]').setValue(' new@example.com ')
|
||||
await wrapper.get('[data-testid="oidc-create-account-send-code"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(sendVerifyCode).toHaveBeenCalledWith({
|
||||
email: 'new@example.com'
|
||||
})
|
||||
})
|
||||
|
||||
it('shows bind-login form for existing account binding and submits credentials with adoption decisions', async () => {
|
||||
exchangePendingOAuthCompletion.mockResolvedValue({
|
||||
error: 'adopt_existing_user_by_email',
|
||||
|
||||
@@ -7,6 +7,7 @@ const {
|
||||
completeWeChatOAuthRegistrationMock,
|
||||
login2FAMock,
|
||||
apiClientPostMock,
|
||||
sendVerifyCodeMock,
|
||||
prepareOAuthBindAccessTokenCookieMock,
|
||||
getAuthTokenMock,
|
||||
replaceMock,
|
||||
@@ -20,6 +21,7 @@ const {
|
||||
completeWeChatOAuthRegistrationMock: vi.fn(),
|
||||
login2FAMock: vi.fn(),
|
||||
apiClientPostMock: vi.fn(),
|
||||
sendVerifyCodeMock: vi.fn(),
|
||||
prepareOAuthBindAccessTokenCookieMock: vi.fn(),
|
||||
getAuthTokenMock: vi.fn(),
|
||||
replaceMock: vi.fn(),
|
||||
@@ -118,6 +120,7 @@ vi.mock('@/api/auth', async () => {
|
||||
exchangePendingOAuthCompletion: (...args: any[]) => exchangePendingOAuthCompletionMock(...args),
|
||||
completeWeChatOAuthRegistration: (...args: any[]) => completeWeChatOAuthRegistrationMock(...args),
|
||||
login2FA: (...args: any[]) => login2FAMock(...args),
|
||||
sendVerifyCode: (...args: any[]) => sendVerifyCodeMock(...args),
|
||||
prepareOAuthBindAccessTokenCookie: (...args: any[]) => prepareOAuthBindAccessTokenCookieMock(...args),
|
||||
getAuthToken: (...args: any[]) => getAuthTokenMock(...args),
|
||||
}
|
||||
@@ -129,6 +132,7 @@ describe('WechatCallbackView', () => {
|
||||
completeWeChatOAuthRegistrationMock.mockReset()
|
||||
login2FAMock.mockReset()
|
||||
apiClientPostMock.mockReset()
|
||||
sendVerifyCodeMock.mockReset()
|
||||
replaceMock.mockReset()
|
||||
setTokenMock.mockReset()
|
||||
showSuccessMock.mockReset()
|
||||
@@ -374,7 +378,7 @@ describe('WechatCallbackView', () => {
|
||||
expect(locationState.current.href).toContain('mode=open')
|
||||
})
|
||||
|
||||
it('collects email for pending oauth account creation and submits adoption decisions', async () => {
|
||||
it('collects email, password, and verify code for pending oauth account creation and submits adoption decisions', async () => {
|
||||
exchangePendingOAuthCompletionMock.mockResolvedValue({
|
||||
error: 'email_required',
|
||||
redirect: '/welcome',
|
||||
@@ -409,11 +413,15 @@ describe('WechatCallbackView', () => {
|
||||
expect(checkboxes).toHaveLength(2)
|
||||
await checkboxes[1].setValue(false)
|
||||
await wrapper.get('[data-testid="wechat-create-account-email"]').setValue(' new@example.com ')
|
||||
await wrapper.get('[data-testid="wechat-create-account-password"]').setValue('secret-123')
|
||||
await wrapper.get('[data-testid="wechat-create-account-verify-code"]').setValue('246810')
|
||||
await wrapper.get('[data-testid="wechat-create-account-submit"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(apiClientPostMock).toHaveBeenCalledWith('/auth/oauth/pending/create-account', {
|
||||
email: 'new@example.com',
|
||||
password: 'secret-123',
|
||||
verify_code: '246810',
|
||||
adopt_display_name: true,
|
||||
adopt_avatar: false,
|
||||
})
|
||||
@@ -421,6 +429,38 @@ describe('WechatCallbackView', () => {
|
||||
expect(replaceMock).toHaveBeenCalledWith('/welcome')
|
||||
})
|
||||
|
||||
it('sends a verify code for pending oauth account creation', async () => {
|
||||
exchangePendingOAuthCompletionMock.mockResolvedValue({
|
||||
error: 'email_required',
|
||||
redirect: '/welcome',
|
||||
})
|
||||
sendVerifyCodeMock.mockResolvedValue({
|
||||
message: 'sent',
|
||||
countdown: 60,
|
||||
})
|
||||
|
||||
const wrapper = mount(WechatCallbackView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AuthLayout: { template: '<div><slot /></div>' },
|
||||
Icon: true,
|
||||
RouterLink: { template: '<a><slot /></a>' },
|
||||
transition: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
await wrapper.get('[data-testid="wechat-create-account-email"]').setValue(' new@example.com ')
|
||||
await wrapper.get('[data-testid="wechat-create-account-send-code"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(sendVerifyCodeMock).toHaveBeenCalledWith({
|
||||
email: 'new@example.com',
|
||||
})
|
||||
})
|
||||
|
||||
it('shows bind-login form for existing account binding and submits credentials with adoption decisions', async () => {
|
||||
exchangePendingOAuthCompletionMock.mockResolvedValue({
|
||||
step: 'bind_login_required',
|
||||
|
||||
Reference in New Issue
Block a user