文件
OmniCard/backend/test/modules/user/external-auth.test.ts
wikiboss 62e0ae4435 feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 18:01:47 +08:00

171 行
6.8 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

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();
}
});
});
});