feat(v2.3): 完成关系表写入闭环
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { lastValueFrom, of, throwError } from 'rxjs';
|
||||
import { ApiTimingInterceptor, resolveApiSlowRequestThreshold } from './api-timing.interceptor';
|
||||
|
||||
describe('ApiTimingInterceptor', () => {
|
||||
const originalEnv = process.env.API_SLOW_REQUEST_MS;
|
||||
|
||||
afterEach(() => {
|
||||
process.env.API_SLOW_REQUEST_MS = originalEnv;
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('uses a safe default threshold when env is missing or invalid', () => {
|
||||
delete process.env.API_SLOW_REQUEST_MS;
|
||||
expect(resolveApiSlowRequestThreshold()).toBe(1000);
|
||||
|
||||
process.env.API_SLOW_REQUEST_MS = 'not-a-number';
|
||||
expect(resolveApiSlowRequestThreshold()).toBe(1000);
|
||||
});
|
||||
|
||||
it('uses a positive configured threshold', () => {
|
||||
process.env.API_SLOW_REQUEST_MS = '250';
|
||||
|
||||
expect(resolveApiSlowRequestThreshold()).toBe(250);
|
||||
});
|
||||
|
||||
it('logs slow HTTP requests with method and url', async () => {
|
||||
process.env.API_SLOW_REQUEST_MS = '10';
|
||||
const interceptor = new ApiTimingInterceptor();
|
||||
const warn = jest.fn();
|
||||
(interceptor as any).logger.warn = warn;
|
||||
jest.spyOn(Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(1025);
|
||||
const context = {
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => ({
|
||||
method: 'PUT',
|
||||
originalUrl: '/api/v1/data/dev-tasks',
|
||||
}),
|
||||
}),
|
||||
};
|
||||
const next = { handle: () => of({ ok: true }) };
|
||||
|
||||
await lastValueFrom(interceptor.intercept(context as any, next as any));
|
||||
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('PUT /api/v1/data/dev-tasks'));
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('25ms'));
|
||||
});
|
||||
|
||||
it('logs slow HTTP requests even when the handler fails', async () => {
|
||||
process.env.API_SLOW_REQUEST_MS = '10';
|
||||
const interceptor = new ApiTimingInterceptor();
|
||||
const warn = jest.fn();
|
||||
(interceptor as any).logger.warn = warn;
|
||||
jest.spyOn(Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(1030);
|
||||
const context = {
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => ({
|
||||
method: 'PATCH',
|
||||
url: '/api/v1/data/bugs',
|
||||
}),
|
||||
}),
|
||||
};
|
||||
const next = { handle: () => throwError(() => new Error('boom')) };
|
||||
|
||||
await expect(lastValueFrom(interceptor.intercept(context as any, next as any))).rejects.toThrow('boom');
|
||||
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('PATCH /api/v1/data/bugs'));
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('30ms'));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
|
||||
import { finalize, Observable } from 'rxjs';
|
||||
|
||||
const DEFAULT_API_SLOW_REQUEST_MS = 1000;
|
||||
|
||||
export function resolveApiSlowRequestThreshold(): number {
|
||||
const parsed = Number(process.env.API_SLOW_REQUEST_MS);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_API_SLOW_REQUEST_MS;
|
||||
return Math.floor(parsed);
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ApiTimingInterceptor implements NestInterceptor {
|
||||
private readonly logger = new Logger(ApiTimingInterceptor.name);
|
||||
private readonly thresholdMs = resolveApiSlowRequestThreshold();
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
||||
const startedAt = Date.now();
|
||||
return next.handle().pipe(
|
||||
finalize(() => {
|
||||
const durationMs = Date.now() - startedAt;
|
||||
if (durationMs < this.thresholdMs) return;
|
||||
|
||||
const request = context.switchToHttp().getRequest<{ method?: string; originalUrl?: string; url?: string }>();
|
||||
const method = request.method ?? 'UNKNOWN';
|
||||
const url = request.originalUrl ?? request.url ?? 'unknown-url';
|
||||
this.logger.warn(`Slow API request: ${method} ${url} ${durationMs}ms`);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user