Merge branch 'develop' into 4-dependency-audit
This commit is contained in:
@@ -18,6 +18,7 @@ const allowedFields = new Set([
|
||||
'grpcCode',
|
||||
'headersReceived',
|
||||
'method',
|
||||
'networkMs',
|
||||
'operation',
|
||||
'queueWaitMs',
|
||||
'requestId',
|
||||
|
||||
@@ -165,6 +165,8 @@ describe('TbankClient operations', () => {
|
||||
operation: 'getAccounts', grpcCode: status.DEADLINE_EXCEEDED,
|
||||
queueWaitMs: 0, channelState: 'CONNECTING', headersReceived: false,
|
||||
}));
|
||||
const [, , fields] = logger.event.mock.calls.at(-1) as [string, string, { networkMs: unknown }];
|
||||
expect(typeof fields.networkMs).toBe('number');
|
||||
expect(JSON.stringify(logger.event.mock.calls)).not.toContain('secret');
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
|
||||
@@ -64,6 +64,7 @@ type TbankClientService = Client & Record<string, Unary<never, never>>;
|
||||
type RpcDiagnostics = {
|
||||
channelState?: string;
|
||||
headersReceived?: boolean;
|
||||
networkMs?: number;
|
||||
queueWaitMs?: number;
|
||||
trackingId?: string;
|
||||
};
|
||||
@@ -524,10 +525,13 @@ export class TbankClient {
|
||||
throw new ServiceUnavailableException({ code: TbankErrorCode.Unavailable });
|
||||
}
|
||||
const response = await this.queue.run(() => {
|
||||
diagnostics.queueWaitMs = Math.round(performance.now() - startedAt);
|
||||
const sentAt = performance.now();
|
||||
diagnostics.queueWaitMs = Math.round(sentAt - startedAt);
|
||||
return this.request<Response>(
|
||||
token, client, method, request, diagnostics,
|
||||
);
|
||||
).finally(() => {
|
||||
diagnostics.networkMs = Math.round(performance.now() - sentAt);
|
||||
});
|
||||
});
|
||||
this.logger.event('debug', 'tbank.rpc.completed', {
|
||||
operation: method,
|
||||
|
||||
@@ -93,7 +93,7 @@ export class StockScreenerItemDto {
|
||||
@ApiProperty({ nullable: true, type: String }) declare currency: null | string;
|
||||
@ApiProperty({ nullable: true, type: String }) declare realExchange: null | string;
|
||||
@ApiProperty({ nullable: true, type: String }) declare countryOfRisk: null | string;
|
||||
@ApiProperty({ nullable: true, type: 'integer' }) declare lot: null | number;
|
||||
@ApiProperty({ nullable: true, minimum: MIN_TICKER_LENGTH, type: 'integer' }) declare lot: null | number;
|
||||
}
|
||||
|
||||
export class StockScreenerPageDto {
|
||||
|
||||
+12
-1
@@ -3,9 +3,20 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
|
||||
export const API_PREFIX = 'api/v1';
|
||||
|
||||
// Nest Swagger cannot express additionalProperties on a class, so closed request
|
||||
// bodies are restored here after generation; the runtime allowlist stays in the controller.
|
||||
const CLOSED_SCHEMAS = ['StockScreenerCriteriaDto'];
|
||||
|
||||
export function createOpenApiDocument(app: INestApplication) {
|
||||
return SwaggerModule.createDocument(
|
||||
const document = SwaggerModule.createDocument(
|
||||
app,
|
||||
new DocumentBuilder().setTitle('WealthLedger API').setVersion('v1').build(),
|
||||
);
|
||||
const schemas = document.components?.schemas ?? {};
|
||||
for (const [name, schema] of Object.entries(schemas)) {
|
||||
if (CLOSED_SCHEMAS.includes(name) && 'properties' in schema) {
|
||||
schema.additionalProperties = false;
|
||||
}
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,13 @@ describe('OpenAPI contract (integration)', () => {
|
||||
});
|
||||
|
||||
it('generates the runtime contract without server startup or deployment configuration', async () => {
|
||||
const schemas = createOpenApiDocument(app).components?.schemas;
|
||||
expect(schemas?.StockScreenerCriteriaDto).toMatchObject({
|
||||
additionalProperties: false,
|
||||
});
|
||||
expect(schemas?.StockScreenerItemDto).toMatchObject({
|
||||
properties: { lot: { type: 'integer', minimum: 1, nullable: true } },
|
||||
});
|
||||
expect(
|
||||
createOpenApiDocument(app).components?.schemas?.ProblemDetailsDto,
|
||||
).toMatchObject({
|
||||
|
||||
Reference in New Issue
Block a user