import { UserExternalAuthService } from '../../../src/modules/user/service/external-auth'; import { AppUserExternalController } from '../../../src/modules/user/controller/app/external'; type ExternalAuthResult = { status: 'not_configured' | 'sent' | 'ok'; token?: string; url?: string; message?: string; }; describe('P11: 外部认证 External Auth (SMS / Magic Link / Google / Apple)', () => { const ORIGINAL_ENV = { SMS_PROVIDER_KEY: process.env.SMS_PROVIDER_KEY, MAIL_SMTP_URL: process.env.MAIL_SMTP_URL, GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, APPLE_CLIENT_ID: process.env.APPLE_CLIENT_ID, GOOGLE_REDIRECT_URI: process.env.GOOGLE_REDIRECT_URI, APPLE_REDIRECT_URI: process.env.APPLE_REDIRECT_URI, }; let service: UserExternalAuthService; beforeEach(() => { service = new UserExternalAuthService(); delete process.env.SMS_PROVIDER_KEY; delete process.env.MAIL_SMTP_URL; delete process.env.GOOGLE_CLIENT_ID; delete process.env.APPLE_CLIENT_ID; delete process.env.GOOGLE_REDIRECT_URI; delete process.env.APPLE_REDIRECT_URI; }); afterEach(() => { for (const [key, value] of Object.entries(ORIGINAL_ENV)) { if (value === undefined) { delete process.env[key]; } else { process.env[key] = value; } } }); function assertNoLoginToken(result: ExternalAuthResult) { expect(result.token).toBeUndefined(); expect(result).not.toHaveProperty('token'); } describe('1. 未配置时不得伪造登录成功', () => { it('smsSend 无 SMS_PROVIDER_KEY 时返回 not_configured 且无 token', async () => { const result = await service.smsSend('13800138000'); expect(result.status).toBe('not_configured'); assertNoLoginToken(result); expect(result.url).toBeUndefined(); }); it('smsLogin 无 SMS_PROVIDER_KEY 时返回 not_configured,任意验证码也不能成功', async () => { const result = await service.smsLogin('13800138000', '0000'); expect(result.status).toBe('not_configured'); assertNoLoginToken(result); const result2 = await service.smsLogin('13800138000', '123456'); expect(result2.status).toBe('not_configured'); assertNoLoginToken(result2); }); it('magicLink 无 MAIL_SMTP_URL 时返回 not_configured,不假装已发信', async () => { const result = await service.magicLink('user@example.com'); expect(result.status).toBe('not_configured'); assertNoLoginToken(result); expect(result.status).not.toBe('sent'); }); it('googleStart 无 GOOGLE_CLIENT_ID 时返回 not_configured,无假授权 URL、无 token', async () => { const result = await service.googleStart(); expect(result.status).toBe('not_configured'); assertNoLoginToken(result); expect(result.url).toBeUndefined(); }); it('appleStart 无 APPLE_CLIENT_ID 时返回 not_configured,无假授权 URL、无 token', async () => { const result = await service.appleStart(); expect(result.status).toBe('not_configured'); assertNoLoginToken(result); expect(result.url).toBeUndefined(); }); }); describe('2. 已配置时的真实能力(不发假 token)', () => { it('有 SMS_PROVIDER_KEY 时 smsSend 返回 sent,仍不带登录 token', async () => { process.env.SMS_PROVIDER_KEY = 'test_sms_key'; const result = await service.smsSend('13800138000'); expect(result.status).toBe('sent'); assertNoLoginToken(result); }); it('有 SMS_PROVIDER_KEY 时任意验证码仍不能直接拿 token', async () => { process.env.SMS_PROVIDER_KEY = 'test_sms_key'; const result = await service.smsLogin('13800138000', '999999'); expect(result.token).toBeUndefined(); expect(result.status).not.toBe('ok'); }); it('有 MAIL_SMTP_URL 时 magicLink 返回 sent,不带 token', async () => { process.env.MAIL_SMTP_URL = 'smtp://user:pass@localhost:1025'; const result = await service.magicLink('user@example.com'); expect(result.status).toBe('sent'); assertNoLoginToken(result); }); it('有 GOOGLE_CLIENT_ID 时返回真实 Google OAuth URL,不带 token', async () => { process.env.GOOGLE_CLIENT_ID = 'google-client-id-abc.apps.googleusercontent.com'; process.env.GOOGLE_REDIRECT_URI = 'https://a9.xxzlw.com/app/user/external/googleCallback'; const result = await service.googleStart(); expect(['sent', 'ok']).toContain(result.status); assertNoLoginToken(result); expect(result.url).toBeDefined(); expect(result.url).toContain('https://accounts.google.com/o/oauth2/v2/auth'); expect(result.url).toContain( encodeURIComponent('google-client-id-abc.apps.googleusercontent.com') ); expect(result.url).toContain('client_id='); expect(result.url).toContain('response_type=code'); }); it('有 APPLE_CLIENT_ID 时返回真实 Apple OAuth URL,不带 token', async () => { process.env.APPLE_CLIENT_ID = 'com.omnicard.service'; process.env.APPLE_REDIRECT_URI = 'https://a9.xxzlw.com/app/user/external/appleCallback'; const result = await service.appleStart(); expect(['sent', 'ok']).toContain(result.status); assertNoLoginToken(result); expect(result.url).toBeDefined(); expect(result.url).toContain('https://appleid.apple.com/auth/authorize'); expect(result.url).toContain('client_id=com.omnicard.service'); expect(result.url).toContain('response_type=code'); }); }); describe('3. 控制器路径约定 /app/user/external/*', () => { it('AppUserExternalController 暴露五个约定方法且返回统一 data 形状', async () => { type OkRes = { code: number; data: ExternalAuthResult }; const controller = new AppUserExternalController(); controller.userExternalAuthService = service; (controller as unknown as { ok: (data?: ExternalAuthResult) => OkRes }).ok = (data?: ExternalAuthResult) => ({ code: 1000, message: 'ok', data: data as ExternalAuthResult, }); const smsSend = (await controller.smsSend({ phone: '13800138000', })) as unknown as OkRes; const smsLogin = (await controller.smsLogin({ phone: '13800138000', code: '1234', })) as unknown as OkRes; const magicLink = (await controller.magicLink({ email: 'a@b.com', })) as unknown as OkRes; const googleStart = (await controller.googleStart()) as unknown as OkRes; const appleStart = (await controller.appleStart()) as unknown as OkRes; for (const res of [smsSend, smsLogin, magicLink, googleStart, appleStart]) { expect(res.code).toBe(1000); expect(res.data.status).toBe('not_configured'); expect(res.data.token).toBeUndefined(); } }); }); });