补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
171 行
6.8 KiB
TypeScript
171 行
6.8 KiB
TypeScript
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();
|
||
}
|
||
});
|
||
});
|
||
});
|