release: prepare RealeseV2.3
This commit is contained in:
@@ -11,6 +11,21 @@ describe('OpenApiService', () => {
|
||||
expect(decryptSecret(encrypted)).toBe('customer-secret');
|
||||
});
|
||||
|
||||
it('returns the configured public HTTPS origin for customer integration parameters', async () => {
|
||||
const previous = process.env.HTTP_API_PUBLIC_ORIGIN;
|
||||
process.env.HTTP_API_PUBLIC_ORIGIN = 'https://api.lisglo.com/';
|
||||
const prisma = {
|
||||
smsApplication: { findFirst: jest.fn().mockResolvedValue({ id: 'app-1', name: '应用A', httpConfig: null, httpIpAllowlist: [] }) },
|
||||
};
|
||||
try {
|
||||
const service = new OpenApiService(prisma as never, {} as never);
|
||||
await expect(service.getConfig('app-1')).resolves.toEqual(expect.objectContaining({ publicOrigin: 'https://api.lisglo.com' }));
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.HTTP_API_PUBLIC_ORIGIN;
|
||||
else process.env.HTTP_API_PUBLIC_ORIGIN = previous;
|
||||
}
|
||||
});
|
||||
|
||||
it('replays a completed request for the same idempotency key and body', async () => {
|
||||
const prisma = {
|
||||
openApiRequest: { findUnique: jest.fn().mockResolvedValue({ bodyHash: 'same', status: 'completed', responseBody: { code: 'ACCEPTED', messageId: 'MSG-1' } }) },
|
||||
|
||||
@@ -68,6 +68,7 @@ export class OpenApiService implements OnModuleInit, OnModuleDestroy {
|
||||
return {
|
||||
applicationId,
|
||||
applicationName: application.name,
|
||||
publicOrigin: httpApiPublicOrigin(),
|
||||
config: application.httpConfig,
|
||||
ipAllowlist: application.httpIpAllowlist.map((item) => item.ipCidr),
|
||||
};
|
||||
@@ -86,7 +87,7 @@ export class OpenApiService implements OnModuleInit, OnModuleDestroy {
|
||||
this.prisma.smsApplicationHttpIpAllowlist.deleteMany({ where: { applicationId } }),
|
||||
...(ipAllowlist.length > 0 ? [this.prisma.smsApplicationHttpIpAllowlist.createMany({ data: ipAllowlist.map((ipCidr) => ({ applicationId, ipCidr })) })] : []),
|
||||
]);
|
||||
return { applicationId, config, ipAllowlist };
|
||||
return { applicationId, publicOrigin: httpApiPublicOrigin(), config, ipAllowlist };
|
||||
}
|
||||
|
||||
async listCredentials(applicationId: string, tenantId?: string) {
|
||||
@@ -419,6 +420,18 @@ export class OpenApiService implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
function httpApiPublicOrigin() {
|
||||
const configured = process.env.HTTP_API_PUBLIC_ORIGIN?.trim().replace(/\/+$/, '');
|
||||
if (!configured) return undefined;
|
||||
const url = new URL(configured);
|
||||
if (url.protocol !== 'https:' || url.username || url.password || url.pathname !== '/' || url.search || url.hash) {
|
||||
// This value is copied into customer integration parameters, so fail closed instead of
|
||||
// publishing an insecure or path-dependent endpoint when deployment config is wrong.
|
||||
throw new Error('HTTP_API_PUBLIC_ORIGIN必须是无路径、无凭据的HTTPS源地址');
|
||||
}
|
||||
return url.origin;
|
||||
}
|
||||
|
||||
function normalizeOpenApiFailure(error: unknown) {
|
||||
if (error instanceof HttpException) {
|
||||
const value = error.getResponse();
|
||||
|
||||
Reference in New Issue
Block a user