forked from wangziqi/gongxue-base
refactor: resolve remaining field audit issues
This commit is contained in:
48
apps/server/src/integration/config/dto/config.dto.spec.ts
Normal file
48
apps/server/src/integration/config/dto/config.dto.spec.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import {
|
||||
DingTalkThirdConfigDto,
|
||||
IntegrationType,
|
||||
SaveIntegrationConfigDto,
|
||||
WeComThirdConfigDto,
|
||||
} from './config.dto';
|
||||
|
||||
const pipe = new ValidationPipe({ transform: true, whitelist: true });
|
||||
|
||||
const transform = (value: unknown) =>
|
||||
pipe.transform(value, { type: 'body', metatype: SaveIntegrationConfigDto });
|
||||
|
||||
describe('integration config request DTO', () => {
|
||||
it('validates and transforms DingTalk configuration', async () => {
|
||||
const result = await transform({
|
||||
type: 'DINGTALK',
|
||||
config: {
|
||||
agentId: 'app-key',
|
||||
corpId: 'corp-id',
|
||||
appSecret: '',
|
||||
appId: 'app-id',
|
||||
ignored: 'value',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.type).toBe(IntegrationType.DINGTALK);
|
||||
expect(result.config).toBeInstanceOf(DingTalkThirdConfigDto);
|
||||
expect(result.config).toMatchObject({ agentId: 'app-key', corpId: 'corp-id', appSecret: '' });
|
||||
expect(result.config).not.toHaveProperty('ignored');
|
||||
});
|
||||
|
||||
it('uses the WeCom nested DTO and removes DingTalk-only fields', async () => {
|
||||
const result = await transform({
|
||||
type: 'WECOM',
|
||||
config: { agentId: 'agent', corpId: 'corp', appId: 'not-supported' },
|
||||
});
|
||||
|
||||
expect(result.config).toBeInstanceOf(WeComThirdConfigDto);
|
||||
expect(result.config).not.toHaveProperty('appId');
|
||||
});
|
||||
|
||||
it('rejects invalid platform types and incomplete nested config', async () => {
|
||||
await expect(transform({ type: 'UNKNOWN', config: {} })).rejects.toThrow();
|
||||
await expect(transform({ type: 'DINGTALK', config: { corpId: 'corp' } })).rejects.toThrow();
|
||||
await expect(transform({ type: 'DINGTALK' })).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -1,27 +1,71 @@
|
||||
/** 钉钉配置 */
|
||||
export interface DingTalkThirdConfig {
|
||||
agentId: string; // AppKey
|
||||
appSecret?: string; // AppSecret;更新已有配置时可留空保留旧值
|
||||
corpId: string; // CorpId
|
||||
appId?: string; // 内部应用ID,用于消息推送(可选)
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsDefined,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
export enum IntegrationType {
|
||||
WECOM = 'WECOM',
|
||||
DINGTALK = 'DINGTALK',
|
||||
}
|
||||
|
||||
/** 企微配置 */
|
||||
export interface WeComThirdConfig {
|
||||
export class DingTalkThirdConfigDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
agentId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
appSecret?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
corpId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
appId?: string;
|
||||
}
|
||||
|
||||
export class WeComThirdConfigDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
agentId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
appSecret?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
corpId: string;
|
||||
}
|
||||
|
||||
export class IntegrationConfigRequestDto {
|
||||
@IsEnum(IntegrationType)
|
||||
type: IntegrationType;
|
||||
|
||||
@IsDefined()
|
||||
@ValidateNested()
|
||||
@Type((options) =>
|
||||
options?.object?.type === IntegrationType.DINGTALK
|
||||
? DingTalkThirdConfigDto
|
||||
: WeComThirdConfigDto,
|
||||
)
|
||||
config: DingTalkThirdConfigDto | WeComThirdConfigDto;
|
||||
}
|
||||
|
||||
export class SaveIntegrationConfigDto extends IntegrationConfigRequestDto {}
|
||||
|
||||
export class TestIntegrationConfigDto extends IntegrationConfigRequestDto {}
|
||||
|
||||
/** 对外返回的配置(脱敏后,不含 appSecret) */
|
||||
export interface ThirdConfigBaseDTO<T = unknown> {
|
||||
type: string;
|
||||
verify?: boolean;
|
||||
config: T;
|
||||
}
|
||||
|
||||
/** 保存配置的请求体 */
|
||||
export interface SaveConfigRequest {
|
||||
type: 'WECOM' | 'DINGTALK';
|
||||
config: DingTalkThirdConfig | WeComThirdConfig;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Controller, Get, Post, Body, Param, UseGuards } from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
|
||||
import { RequirePermission } from '../../auth/decorators/permission.decorator';
|
||||
import { IntegrationConfigService } from './integration-config.service';
|
||||
import type { SaveConfigRequest } from './dto/config.dto';
|
||||
import { SaveIntegrationConfigDto, TestIntegrationConfigDto } from './dto/config.dto';
|
||||
|
||||
@Controller('integration/config')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@@ -31,7 +31,7 @@ export class IntegrationConfigController {
|
||||
/** 保存配置 */
|
||||
@Post()
|
||||
@RequirePermission('integration:trigger')
|
||||
async saveConfig(@Body() body: SaveConfigRequest) {
|
||||
async saveConfig(@Body() body: SaveIntegrationConfigDto) {
|
||||
await this.service.saveConfig(body);
|
||||
return { success: true, message: '配置已保存' };
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export class IntegrationConfigController {
|
||||
/** 测试连接 */
|
||||
@Post('test')
|
||||
@RequirePermission('integration:read')
|
||||
async testConnection(@Body() body: SaveConfigRequest) {
|
||||
async testConnection(@Body() body: TestIntegrationConfigDto) {
|
||||
const success = await this.service.testConnection(body.type, body.config);
|
||||
return { success, message: success ? '连接成功' : '连接失败,请检查配置信息' };
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { Injectable, Logger, BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import {
|
||||
IntegrationConfig,
|
||||
IntegrationConfigDetail,
|
||||
} from '../entities/integration-config.entity';
|
||||
import { IntegrationConfig, IntegrationConfigDetail } from '../entities/integration-config.entity';
|
||||
import {
|
||||
ThirdConfigBaseDTO,
|
||||
DingTalkThirdConfig,
|
||||
WeComThirdConfig,
|
||||
SaveConfigRequest,
|
||||
DingTalkThirdConfigDto,
|
||||
WeComThirdConfigDto,
|
||||
IntegrationType,
|
||||
SaveIntegrationConfigDto,
|
||||
} from './dto/config.dto';
|
||||
|
||||
@Injectable()
|
||||
@@ -67,7 +65,7 @@ export class IntegrationConfigService {
|
||||
}
|
||||
|
||||
/** 保存/更新配置 */
|
||||
async saveConfig(request: SaveConfigRequest): Promise<void> {
|
||||
async saveConfig(request: SaveIntegrationConfigDto): Promise<void> {
|
||||
const config = await this.ensureConfig();
|
||||
const detailType = this.getDetailType(request.type);
|
||||
|
||||
@@ -121,8 +119,8 @@ export class IntegrationConfigService {
|
||||
|
||||
/** 测试连接 */
|
||||
async testConnection(
|
||||
type: string,
|
||||
config: DingTalkThirdConfig | WeComThirdConfig,
|
||||
type: IntegrationType,
|
||||
config: DingTalkThirdConfigDto | WeComThirdConfigDto,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const finalConfig = { ...config } as Record<string, unknown>;
|
||||
|
||||
Reference in New Issue
Block a user