补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
133 行
4.4 KiB
TypeScript
133 行
4.4 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { CoolCommException } from '@cool-midway/core';
|
|
import { CardInfoEntity } from '../../../src/modules/card/entity/info';
|
|
import { CardInfoService } from '../../../src/modules/card/service/info';
|
|
import { AdminCardInfoController } from '../../../src/modules/card/controller/admin/info';
|
|
|
|
/**
|
|
* 内存 Repository,仅覆盖 findOneBy
|
|
*/
|
|
class MemoryCardInfoRepo {
|
|
private data: CardInfoEntity[] = [];
|
|
|
|
constructor(initialData: Array<Partial<CardInfoEntity>> = []) {
|
|
this.data = initialData.map(item => {
|
|
const entity = new CardInfoEntity();
|
|
Object.assign(entity, item);
|
|
return entity;
|
|
});
|
|
}
|
|
|
|
async findOneBy(
|
|
where: Partial<Pick<CardInfoEntity, 'id'>>
|
|
): Promise<CardInfoEntity | null> {
|
|
const found = this.data.find(item => {
|
|
if (where.id === undefined) {
|
|
return false;
|
|
}
|
|
return Number(item.id) === Number(where.id);
|
|
});
|
|
return found ? ({ ...found } as CardInfoEntity) : null;
|
|
}
|
|
}
|
|
|
|
describe('后台名片 vCard 导出 API', () => {
|
|
const controllerFile = path.join(
|
|
__dirname,
|
|
'../../../src/modules/card/controller/admin/info.ts'
|
|
);
|
|
const serviceFile = path.join(
|
|
__dirname,
|
|
'../../../src/modules/card/service/info.ts'
|
|
);
|
|
|
|
describe('1. 路由与方法约定', () => {
|
|
it('admin info 控制器应声明 /vcard 导出接口', () => {
|
|
expect(fs.existsSync(controllerFile)).toBe(true);
|
|
const content = fs.readFileSync(controllerFile, 'utf-8');
|
|
expect(content).toMatch(/@Post\(\s*['"]\/vcard['"]/);
|
|
expect(content).toMatch(/exportVCard|exportVcard/);
|
|
});
|
|
|
|
it('CardInfoService 应提供 exportVCard 方法', () => {
|
|
expect(fs.existsSync(serviceFile)).toBe(true);
|
|
const content = fs.readFileSync(serviceFile, 'utf-8');
|
|
expect(content).toContain('exportVCard');
|
|
expect(content).toContain('generateVCard');
|
|
expect(content).not.toContain("FN:假");
|
|
});
|
|
});
|
|
|
|
describe('2. exportVCard 业务', () => {
|
|
let service: CardInfoService;
|
|
|
|
beforeEach(() => {
|
|
service = new CardInfoService();
|
|
service.cardInfoEntity = new MemoryCardInfoRepo([
|
|
{
|
|
id: 42,
|
|
name: 'Alex Morgan',
|
|
title: 'CTO',
|
|
company: 'OmniCard AI',
|
|
phone: '+86-13800138000',
|
|
email: 'alex@omnicard.ai',
|
|
slug: 'alex-morgan',
|
|
status: 'published',
|
|
},
|
|
]) as unknown as CardInfoService['cardInfoEntity'];
|
|
});
|
|
|
|
it('名片不存在时应抛出 CoolCommException,不得返回写死姓名', async () => {
|
|
await expect(service.exportVCard(99999)).rejects.toThrow(CoolCommException);
|
|
await expect(service.exportVCard(99999)).rejects.toThrow(/不存在|not found/i);
|
|
});
|
|
|
|
it('名片存在时返回的 vcf 必须含 BEGIN:VCARD 且包含真实姓名', async () => {
|
|
const vcf = await service.exportVCard(42);
|
|
expect(vcf).toContain('BEGIN:VCARD');
|
|
expect(vcf).toContain('END:VCARD');
|
|
expect(vcf).toContain('FN:Alex Morgan');
|
|
expect(vcf).not.toContain('假姓名');
|
|
});
|
|
});
|
|
|
|
describe('3. 控制器委派', () => {
|
|
it('vcard 接口应按 id 调用 service.exportVCard 并 ok 返回', async () => {
|
|
const controller = new AdminCardInfoController();
|
|
const exportVCard = jest.fn().mockResolvedValue(
|
|
'BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Alex Morgan\r\nEND:VCARD\r\n'
|
|
);
|
|
controller.cardInfoService = {
|
|
exportVCard,
|
|
} as unknown as CardInfoService;
|
|
controller.ok = jest.fn((data: unknown) => ({ code: 1000, data })) as never;
|
|
|
|
const result = await controller.vcard({ id: 42 });
|
|
|
|
expect(exportVCard).toHaveBeenCalledWith(42);
|
|
expect(controller.ok).toHaveBeenCalled();
|
|
const okArg = (controller.ok as jest.Mock).mock.calls[0][0] as {
|
|
vcard?: string;
|
|
filename?: string;
|
|
};
|
|
expect(okArg.vcard).toContain('BEGIN:VCARD');
|
|
expect(okArg.filename).toMatch(/\.vcf$/);
|
|
expect(result).toEqual({
|
|
code: 1000,
|
|
data: okArg,
|
|
});
|
|
});
|
|
|
|
it('缺少 id 时应抛 CoolCommException', async () => {
|
|
const controller = new AdminCardInfoController();
|
|
controller.cardInfoService = {
|
|
exportVCard: jest.fn(),
|
|
} as unknown as CardInfoService;
|
|
|
|
await expect(controller.vcard({})).rejects.toThrow(CoolCommException);
|
|
await expect(controller.vcard({ id: 0 })).rejects.toThrow(CoolCommException);
|
|
});
|
|
});
|
|
});
|