mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-29 16:20:27 +00:00
修复邮箱域名注册额度策略
This commit is contained in:
@@ -119,9 +119,9 @@ export default {
|
||||
emailVerificationHint: 'Require email verification for new registrations',
|
||||
emailSuffixWhitelist: 'Email Domain Whitelist',
|
||||
emailSuffixWhitelistHint:
|
||||
"Only email addresses from the specified domains can register (for example, {'@'}qq.com, {'@'}gmail.com, *.edu.cn)",
|
||||
"Emails from allowlist domains can register without a quota. When the allowlist is not empty, every other registrable domain can register one account. Empty the allowlist to remove the quota for all domains (for example, {'@'}qq.com, {'@'}gmail.com, *.edu.cn).",
|
||||
emailSuffixWhitelistPlaceholder: "{'@'}example.com, *.edu.cn",
|
||||
emailSuffixWhitelistInputHint: 'Leave empty for no restriction. Use *.edu.cn to match edu.cn and its subdomains.',
|
||||
emailSuffixWhitelistInputHint: 'Empty the allowlist to remove the registration quota. Use *.edu.cn to match edu.cn and its subdomains.',
|
||||
promoCode: 'Promo Code',
|
||||
promoCodeHint: 'Allow users to use promo codes during registration',
|
||||
invitationCode: 'Invitation Code Registration',
|
||||
|
||||
@@ -234,6 +234,8 @@ export default {
|
||||
USER_NOT_ACTIVE: 'Account has been disabled.',
|
||||
},
|
||||
registrationFailed: 'Registration failed. Please try again.',
|
||||
emailDomainRegistrationLimit:
|
||||
'This email domain cannot register another account. Please use a mainstream email, or contact support to add your enterprise domain to the allowlist.',
|
||||
emailSuffixNotAllowed: 'This email domain is not allowed for registration.',
|
||||
emailSuffixNotAllowedWithAllowed:
|
||||
'This email domain is not allowed. Allowed domains: {suffixes}',
|
||||
|
||||
@@ -119,9 +119,9 @@ export default {
|
||||
emailVerificationHint: '新用户注册时需要验证邮箱',
|
||||
emailSuffixWhitelist: '邮箱域名白名单',
|
||||
emailSuffixWhitelistHint:
|
||||
"仅允许使用指定域名的邮箱注册账号(例如 {'@'}qq.com, {'@'}gmail.com, *.edu.cn)",
|
||||
"白名单域名的邮箱可无限注册;白名单非空时,其他可注册主域名各限注册一个账户。清空白名单后,所有域名均不限制注册数量(例如 {'@'}qq.com, {'@'}gmail.com, *.edu.cn)",
|
||||
emailSuffixWhitelistPlaceholder: "{'@'}example.com, *.edu.cn",
|
||||
emailSuffixWhitelistInputHint: '留空则不限制。使用 *.edu.cn 可匹配 edu.cn 及其子域名。',
|
||||
emailSuffixWhitelistInputHint: '清空白名单后不限制注册数量。使用 *.edu.cn 可匹配 edu.cn 及其子域名。',
|
||||
promoCode: '优惠码',
|
||||
promoCodeHint: '允许用户在注册时使用优惠码',
|
||||
invitationCode: '邀请码注册',
|
||||
|
||||
@@ -234,6 +234,8 @@ export default {
|
||||
USER_NOT_ACTIVE: '账号已被禁用',
|
||||
},
|
||||
registrationFailed: '注册失败,请重试。',
|
||||
emailDomainRegistrationLimit:
|
||||
'该邮箱域名无法注册新账户。请使用主流邮箱注册;如需使用企业邮箱,请联系客服添加域名白名单。',
|
||||
emailSuffixNotAllowed: '该邮箱域名不在允许注册范围内。',
|
||||
emailSuffixNotAllowedWithAllowed: '该邮箱域名不被允许。可用域名:{suffixes}',
|
||||
emailSuffixAllowedMore: '等 {count} 项',
|
||||
|
||||
@@ -2,6 +2,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { defineComponent, h } from "vue";
|
||||
import { flushPromises, mount } from "@vue/test-utils";
|
||||
|
||||
import enCommon from "@/i18n/locales/en/common";
|
||||
import enSettings from "@/i18n/locales/en/admin/settings";
|
||||
import zhCommon from "@/i18n/locales/zh/common";
|
||||
import zhSettings from "@/i18n/locales/zh/admin/settings";
|
||||
import SettingsView from "../SettingsView.vue";
|
||||
|
||||
const {
|
||||
@@ -592,6 +596,22 @@ async function openUsersTab(wrapper: ReturnType<typeof mountView>) {
|
||||
await flushPromises();
|
||||
}
|
||||
|
||||
describe("admin SettingsView email domain quota copy", () => {
|
||||
it("documents the email domain quota and empty-whitelist behavior in both locales", () => {
|
||||
expect(zhCommon.auth.emailDomainRegistrationLimit).toContain("主流邮箱");
|
||||
expect(zhCommon.auth.emailDomainRegistrationLimit).toContain("联系客服");
|
||||
expect(enCommon.auth.emailDomainRegistrationLimit).toContain("mainstream email");
|
||||
expect(enCommon.auth.emailDomainRegistrationLimit).toContain("contact support");
|
||||
|
||||
const zhHint = zhSettings.settings.registration.emailSuffixWhitelistHint;
|
||||
const enHint = enSettings.settings.registration.emailSuffixWhitelistHint;
|
||||
expect(zhHint).toContain("其他可注册主域名各限注册一个账户");
|
||||
expect(zhHint).toContain("清空白名单");
|
||||
expect(enHint).toContain("one account");
|
||||
expect(enHint).toContain("empty");
|
||||
});
|
||||
});
|
||||
|
||||
describe("admin SettingsView payment visible method controls", () => {
|
||||
beforeEach(() => {
|
||||
getSettings.mockReset();
|
||||
|
||||
@@ -195,18 +195,14 @@ import {
|
||||
} from '@/api/auth'
|
||||
import { apiClient } from '@/api/client'
|
||||
import { buildAuthErrorMessage } from '@/utils/authError'
|
||||
import {
|
||||
formatRegistrationEmailSuffixWhitelistForMessage,
|
||||
isRegistrationEmailSuffixAllowed,
|
||||
normalizeRegistrationEmailSuffixWhitelist
|
||||
} from '@/utils/registrationEmailPolicy'
|
||||
import { extractApiErrorCode } from '@/utils/apiError'
|
||||
import {
|
||||
clearAllAffiliateReferralCodes,
|
||||
loadAffiliateReferralCode,
|
||||
oauthAffiliatePayload
|
||||
} from '@/utils/oauthAffiliate'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const { t } = useI18n()
|
||||
|
||||
// ==================== Router & Stores ====================
|
||||
|
||||
@@ -270,7 +266,6 @@ const aliyunCaptchaSceneId = ref<string>('')
|
||||
const aliyunCaptchaPrefix = ref<string>('')
|
||||
const aliyunCaptchaRegion = ref<string>('cn')
|
||||
const siteName = ref<string>('Sub2API')
|
||||
const registrationEmailSuffixWhitelist = ref<string[]>([])
|
||||
|
||||
// Turnstile for resend
|
||||
const turnstileRef = ref<InstanceType<typeof TurnstileWidget> | null>(null)
|
||||
@@ -370,9 +365,6 @@ onMounted(async () => {
|
||||
aliyunCaptchaPrefix.value = settings.aliyun_captcha_prefix || ''
|
||||
aliyunCaptchaRegion.value = settings.aliyun_captcha_region || 'cn'
|
||||
siteName.value = settings.site_name || 'Sub2API'
|
||||
registrationEmailSuffixWhitelist.value = normalizeRegistrationEmailSuffixWhitelist(
|
||||
settings.registration_email_suffix_whitelist || []
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to load public settings:', error)
|
||||
}
|
||||
@@ -481,10 +473,6 @@ function isPendingOAuthFlow(): boolean {
|
||||
return Boolean(pendingProvider.value.trim())
|
||||
}
|
||||
|
||||
function shouldBypassRegistrationEmailPolicy(): boolean {
|
||||
return isPendingOAuthFlow() || Boolean(pendingAuthToken.value.trim())
|
||||
}
|
||||
|
||||
function resolvePendingOAuthCallbackRoute(provider: string): string {
|
||||
switch (provider.trim().toLowerCase()) {
|
||||
case 'linuxdo':
|
||||
@@ -526,12 +514,6 @@ async function sendCode(): Promise<void> {
|
||||
let captchaProofUsed = false
|
||||
|
||||
try {
|
||||
if (!shouldBypassRegistrationEmailPolicy() && !isRegistrationEmailSuffixAllowed(email.value, registrationEmailSuffixWhitelist.value)) {
|
||||
errorMessage.value = buildEmailSuffixNotAllowedMessage()
|
||||
appStore.showError(errorMessage.value)
|
||||
return
|
||||
}
|
||||
|
||||
const requestPayload = {
|
||||
email: email.value,
|
||||
[pendingAuthTokenField.value]: pendingAuthToken.value || undefined,
|
||||
@@ -575,9 +557,7 @@ async function sendCode(): Promise<void> {
|
||||
|
||||
showResendTurnstile.value = false
|
||||
} catch (error: unknown) {
|
||||
errorMessage.value = buildAuthErrorMessage(error, {
|
||||
fallback: t('auth.sendCodeFailed')
|
||||
})
|
||||
errorMessage.value = buildRegistrationErrorMessage(error, t('auth.sendCodeFailed'))
|
||||
|
||||
appStore.showError(errorMessage.value)
|
||||
} finally {
|
||||
@@ -655,12 +635,6 @@ async function handleVerify(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
if (!shouldBypassRegistrationEmailPolicy() && !isRegistrationEmailSuffixAllowed(email.value, registrationEmailSuffixWhitelist.value)) {
|
||||
errorMessage.value = buildEmailSuffixNotAllowedMessage()
|
||||
appStore.showError(errorMessage.value)
|
||||
return
|
||||
}
|
||||
|
||||
if (!(await acquireCreateAccountActionProof())) {
|
||||
return
|
||||
}
|
||||
@@ -740,9 +714,7 @@ async function handleVerify(): Promise<void> {
|
||||
// Redirect to dashboard
|
||||
await router.push(pendingRedirect.value || '/dashboard')
|
||||
} catch (error: unknown) {
|
||||
errorMessage.value = buildAuthErrorMessage(error, {
|
||||
fallback: t('auth.verifyFailed')
|
||||
})
|
||||
errorMessage.value = buildRegistrationErrorMessage(error, t('auth.verifyFailed'))
|
||||
|
||||
appStore.showError(errorMessage.value)
|
||||
} finally {
|
||||
@@ -763,20 +735,11 @@ function handleBack(): void {
|
||||
router.push('/register')
|
||||
}
|
||||
|
||||
function buildEmailSuffixNotAllowedMessage(): string {
|
||||
const normalizedWhitelist = normalizeRegistrationEmailSuffixWhitelist(
|
||||
registrationEmailSuffixWhitelist.value
|
||||
)
|
||||
if (normalizedWhitelist.length === 0) {
|
||||
return t('auth.emailSuffixNotAllowed')
|
||||
function buildRegistrationErrorMessage(error: unknown, fallback: string): string {
|
||||
if (extractApiErrorCode(error) === 'EMAIL_DOMAIN_REGISTRATION_LIMIT') {
|
||||
return t('auth.emailDomainRegistrationLimit')
|
||||
}
|
||||
const separator = String(locale.value || '').toLowerCase().startsWith('zh') ? '、' : ', '
|
||||
return t('auth.emailSuffixNotAllowedWithAllowed', {
|
||||
suffixes: formatRegistrationEmailSuffixWhitelistForMessage(normalizedWhitelist, {
|
||||
separator,
|
||||
more: (count) => t('auth.emailSuffixAllowedMore', { count })
|
||||
})
|
||||
})
|
||||
return buildAuthErrorMessage(error, { fallback })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -353,12 +353,7 @@ import {
|
||||
validateInvitationCode
|
||||
} from '@/api/auth'
|
||||
import { buildAuthErrorMessage } from '@/utils/authError'
|
||||
import { extractI18nErrorMessage } from '@/utils/apiError'
|
||||
import {
|
||||
formatRegistrationEmailSuffixWhitelistForMessage,
|
||||
isRegistrationEmailSuffixAllowed,
|
||||
normalizeRegistrationEmailSuffixWhitelist
|
||||
} from '@/utils/registrationEmailPolicy'
|
||||
import { extractApiErrorCode, extractI18nErrorMessage } from '@/utils/apiError'
|
||||
import {
|
||||
clearAffiliateReferralCode,
|
||||
loadAffiliateReferralCode,
|
||||
@@ -366,7 +361,7 @@ import {
|
||||
} from '@/utils/oauthAffiliate'
|
||||
import type { LoginAgreementDocument } from '@/types'
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const { t } = useI18n()
|
||||
const LOGIN_AGREEMENT_STORAGE_KEY = 'sub2api_login_agreement_consent'
|
||||
|
||||
// ==================== Router & Stores ====================
|
||||
@@ -405,7 +400,6 @@ const oidcOAuthEnabled = ref<boolean>(false)
|
||||
const oidcOAuthProviderName = ref<string>('OIDC')
|
||||
const githubOAuthEnabled = ref<boolean>(false)
|
||||
const googleOAuthEnabled = ref<boolean>(false)
|
||||
const registrationEmailSuffixWhitelist = ref<string[]>([])
|
||||
const loginAgreementEnabled = ref<boolean>(false)
|
||||
const loginAgreementMode = ref<'modal' | 'checkbox' | string>('modal')
|
||||
const loginAgreementUpdatedAt = ref<string>('')
|
||||
@@ -538,9 +532,6 @@ onMounted(async () => {
|
||||
oidcOAuthProviderName.value = settings.oidc_oauth_provider_name || 'OIDC'
|
||||
githubOAuthEnabled.value = settings.github_oauth_enabled
|
||||
googleOAuthEnabled.value = settings.google_oauth_enabled
|
||||
registrationEmailSuffixWhitelist.value = normalizeRegistrationEmailSuffixWhitelist(
|
||||
settings.registration_email_suffix_whitelist || []
|
||||
)
|
||||
applyLoginAgreementSettings(settings)
|
||||
|
||||
// Read promo code from URL parameter only if promo code is enabled
|
||||
@@ -859,22 +850,6 @@ function validateEmail(email: string): boolean {
|
||||
return emailRegex.test(email)
|
||||
}
|
||||
|
||||
function buildEmailSuffixNotAllowedMessage(): string {
|
||||
const normalizedWhitelist = normalizeRegistrationEmailSuffixWhitelist(
|
||||
registrationEmailSuffixWhitelist.value
|
||||
)
|
||||
if (normalizedWhitelist.length === 0) {
|
||||
return t('auth.emailSuffixNotAllowed')
|
||||
}
|
||||
const separator = String(locale.value || '').toLowerCase().startsWith('zh') ? '、' : ', '
|
||||
return t('auth.emailSuffixNotAllowedWithAllowed', {
|
||||
suffixes: formatRegistrationEmailSuffixWhitelistForMessage(normalizedWhitelist, {
|
||||
separator,
|
||||
more: (count) => t('auth.emailSuffixAllowedMore', { count })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function validateForm(): boolean {
|
||||
// Reset errors
|
||||
errors.email = ''
|
||||
@@ -899,11 +874,6 @@ function validateForm(): boolean {
|
||||
} else if (!validateEmail(formData.email)) {
|
||||
errors.email = t('auth.invalidEmail')
|
||||
isValid = false
|
||||
} else if (
|
||||
!isRegistrationEmailSuffixAllowed(formData.email, registrationEmailSuffixWhitelist.value)
|
||||
) {
|
||||
errors.email = buildEmailSuffixNotAllowedMessage()
|
||||
isValid = false
|
||||
}
|
||||
|
||||
// Password validation
|
||||
@@ -1037,9 +1007,7 @@ async function handleRegister(): Promise<void> {
|
||||
await router.push('/dashboard')
|
||||
} catch (error: unknown) {
|
||||
// Handle registration error
|
||||
errorMessage.value = buildAuthErrorMessage(error, {
|
||||
fallback: t('auth.registrationFailed')
|
||||
})
|
||||
errorMessage.value = buildRegistrationErrorMessage(error, t('auth.registrationFailed'))
|
||||
|
||||
// Also show error toast
|
||||
appStore.showError(errorMessage.value)
|
||||
@@ -1050,6 +1018,13 @@ async function handleRegister(): Promise<void> {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function buildRegistrationErrorMessage(error: unknown, fallback: string): string {
|
||||
if (extractApiErrorCode(error) === 'EMAIL_DOMAIN_REGISTRATION_LIMIT') {
|
||||
return t('auth.emailDomainRegistrationLimit')
|
||||
}
|
||||
return buildAuthErrorMessage(error, { fallback })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -64,6 +64,9 @@ vi.mock('vue-i18n', () => ({
|
||||
if (key === 'auth.accountCreatedSuccess') {
|
||||
return `Account created for ${params?.siteName ?? 'Sub2API'}`
|
||||
}
|
||||
if (key === 'auth.emailDomainRegistrationLimit') {
|
||||
return '该邮箱域名无法注册新账户。请使用主流邮箱注册;如需使用企业邮箱,请联系客服添加域名白名单。'
|
||||
}
|
||||
return key
|
||||
},
|
||||
locale: { value: 'en' },
|
||||
@@ -307,6 +310,118 @@ describe('EmailVerifyView', () => {
|
||||
expect(showErrorMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sends a verification code for a non-whitelist email domain', async () => {
|
||||
getPublicSettingsMock.mockResolvedValue({
|
||||
turnstile_enabled: false,
|
||||
turnstile_site_key: '',
|
||||
site_name: 'Sub2API',
|
||||
registration_email_suffix_whitelist: ['allowed.com'],
|
||||
})
|
||||
sessionStorage.setItem(
|
||||
'register_data',
|
||||
JSON.stringify({
|
||||
email: 'first@custom.example',
|
||||
password: 'secret-123',
|
||||
})
|
||||
)
|
||||
|
||||
mount(EmailVerifyView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AuthLayout: { template: '<div><slot /><slot name="footer" /></div>' },
|
||||
Icon: true,
|
||||
TurnstileWidget: true,
|
||||
transition: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(sendVerifyCodeMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ email: 'first@custom.example' })
|
||||
)
|
||||
expect(showErrorMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows the localized domain quota message when sending a verification code is rejected', async () => {
|
||||
getPublicSettingsMock.mockResolvedValue({
|
||||
turnstile_enabled: false,
|
||||
turnstile_site_key: '',
|
||||
site_name: 'Sub2API',
|
||||
registration_email_suffix_whitelist: ['allowed.com'],
|
||||
})
|
||||
sendVerifyCodeMock.mockRejectedValueOnce({
|
||||
reason: 'EMAIL_DOMAIN_REGISTRATION_LIMIT',
|
||||
message: 'raw backend message',
|
||||
})
|
||||
sessionStorage.setItem(
|
||||
'register_data',
|
||||
JSON.stringify({
|
||||
email: 'second@custom.example',
|
||||
password: 'secret-123',
|
||||
})
|
||||
)
|
||||
|
||||
mount(EmailVerifyView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AuthLayout: { template: '<div><slot /><slot name="footer" /></div>' },
|
||||
Icon: true,
|
||||
TurnstileWidget: true,
|
||||
transition: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(showErrorMock).toHaveBeenLastCalledWith(
|
||||
'该邮箱域名无法注册新账户。请使用主流邮箱注册;如需使用企业邮箱,请联系客服添加域名白名单。'
|
||||
)
|
||||
})
|
||||
|
||||
it('shows the localized domain quota message when verified registration is rejected', async () => {
|
||||
getPublicSettingsMock.mockResolvedValue({
|
||||
turnstile_enabled: false,
|
||||
turnstile_site_key: '',
|
||||
site_name: 'Sub2API',
|
||||
registration_email_suffix_whitelist: ['allowed.com'],
|
||||
})
|
||||
sessionStorage.setItem(
|
||||
'register_data',
|
||||
JSON.stringify({
|
||||
email: 'second@custom.example',
|
||||
password: 'secret-123',
|
||||
})
|
||||
)
|
||||
registerMock.mockRejectedValueOnce({
|
||||
reason: 'EMAIL_DOMAIN_REGISTRATION_LIMIT',
|
||||
message: 'raw backend message',
|
||||
})
|
||||
|
||||
const wrapper = mount(EmailVerifyView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AuthLayout: { template: '<div><slot /><slot name="footer" /></div>' },
|
||||
Icon: true,
|
||||
TurnstileWidget: true,
|
||||
transition: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
await wrapper.get('#code').setValue('123456')
|
||||
await wrapper.get('form').trigger('submit.prevent')
|
||||
await flushPromises()
|
||||
|
||||
expect(registerMock).toHaveBeenCalled()
|
||||
expect(showErrorMock).toHaveBeenLastCalledWith(
|
||||
'该邮箱域名无法注册新账户。请使用主流邮箱注册;如需使用企业邮箱,请联系客服添加域名白名单。'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the pending oauth verify-code endpoint when auth store only carries the pending provider', async () => {
|
||||
authStoreState.pendingAuthSession = {
|
||||
token: '',
|
||||
|
||||
@@ -2,8 +2,10 @@ import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import RegisterView from '@/views/auth/RegisterView.vue'
|
||||
|
||||
const { getPublicSettingsMock } = vi.hoisted(() => ({
|
||||
getPublicSettingsMock: vi.fn()
|
||||
const { getPublicSettingsMock, registerMock, showErrorMock } = vi.hoisted(() => ({
|
||||
getPublicSettingsMock: vi.fn(),
|
||||
registerMock: vi.fn(),
|
||||
showErrorMock: vi.fn()
|
||||
}))
|
||||
|
||||
const publicSettings = {
|
||||
@@ -35,15 +37,18 @@ vi.mock('vue-i18n', () => ({
|
||||
}
|
||||
}),
|
||||
useI18n: () => ({
|
||||
t: (key: string) => key,
|
||||
t: (key: string) =>
|
||||
key === 'auth.emailDomainRegistrationLimit'
|
||||
? '该邮箱域名无法注册新账户。请使用主流邮箱注册;如需使用企业邮箱,请联系客服添加域名白名单。'
|
||||
: key,
|
||||
locale: { value: 'en' }
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/stores', () => ({
|
||||
useAuthStore: () => ({ register: vi.fn() }),
|
||||
useAuthStore: () => ({ register: (...args: unknown[]) => registerMock(...args) }),
|
||||
useAppStore: () => ({
|
||||
showError: vi.fn(),
|
||||
showError: (...args: unknown[]) => showErrorMock(...args),
|
||||
showSuccess: vi.fn(),
|
||||
showWarning: vi.fn()
|
||||
})
|
||||
@@ -79,7 +84,10 @@ function mountRegister() {
|
||||
describe('RegisterView invitation layout', () => {
|
||||
beforeEach(() => {
|
||||
getPublicSettingsMock.mockReset()
|
||||
registerMock.mockReset()
|
||||
showErrorMock.mockReset()
|
||||
getPublicSettingsMock.mockResolvedValue(publicSettings)
|
||||
registerMock.mockResolvedValue({})
|
||||
})
|
||||
|
||||
it('keeps the optional affiliate invitation field before Turnstile', async () => {
|
||||
@@ -109,4 +117,47 @@ describe('RegisterView invitation layout', () => {
|
||||
expect(wrapper.find('[data-testid="affiliate-invitation-field"]').exists()).toBe(false)
|
||||
expect(wrapper.get('#invitation_code').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('submits a non-whitelist email domain so the backend can enforce its registration quota', async () => {
|
||||
getPublicSettingsMock.mockResolvedValueOnce({
|
||||
...publicSettings,
|
||||
turnstile_enabled: false,
|
||||
registration_email_suffix_whitelist: ['allowed.com']
|
||||
})
|
||||
|
||||
const wrapper = mountRegister()
|
||||
await flushPromises()
|
||||
await wrapper.get('#email').setValue('first@custom.example')
|
||||
await wrapper.get('#password').setValue('secret-123')
|
||||
await wrapper.get('form').trigger('submit.prevent')
|
||||
await flushPromises()
|
||||
|
||||
expect(registerMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ email: 'first@custom.example' })
|
||||
)
|
||||
expect(showErrorMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows the localized registration domain quota message returned by the backend', async () => {
|
||||
getPublicSettingsMock.mockResolvedValueOnce({
|
||||
...publicSettings,
|
||||
turnstile_enabled: false,
|
||||
registration_email_suffix_whitelist: ['allowed.com']
|
||||
})
|
||||
registerMock.mockRejectedValueOnce({
|
||||
reason: 'EMAIL_DOMAIN_REGISTRATION_LIMIT',
|
||||
message: 'raw backend message'
|
||||
})
|
||||
|
||||
const wrapper = mountRegister()
|
||||
await flushPromises()
|
||||
await wrapper.get('#email').setValue('second@custom.example')
|
||||
await wrapper.get('#password').setValue('secret-123')
|
||||
await wrapper.get('form').trigger('submit.prevent')
|
||||
await flushPromises()
|
||||
|
||||
expect(showErrorMock).toHaveBeenCalledWith(
|
||||
'该邮箱域名无法注册新账户。请使用主流邮箱注册;如需使用企业邮箱,请联系客服添加域名白名单。'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user