fix(investments): align roles and date contracts
Backend security / security (push) Successful in 9s
Backend verify / verify (push) Successful in 10m23s
Backend security / security (pull_request) Successful in 8s
Backend verify / verify (pull_request) Successful in 10m39s
Conventional commits / commits (pull_request) Successful in 29s

Identify income dependencies by role and document screen-specific dates.
Convert inclusive calendar days into half-open source ranges while
preserving operation timestamps and existing stored coverage.
This commit is contained in:
2026-09-27 11:38:13 +03:00
parent e4232138d0
commit 516e43203e
18 changed files with 500 additions and 74 deletions
+104
View File
@@ -1,5 +1,6 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
@@ -22,6 +23,109 @@ describe('OpenAPI contract (integration)', () => {
afterAll(() => app.close());
it('distinguishes calendar screen dates from operation instants in requests and responses', () => {
const document = createOpenApiDocument(app);
const schemas = document.components?.schemas;
const screenSchema = {
oneOf: [
{ $ref: '#/components/schemas/IncomeInvestmentScreenDto' },
{ $ref: '#/components/schemas/EventsInvestmentScreenDto' },
{ $ref: '#/components/schemas/TimestampInvestmentScreenDto' },
],
discriminator: {
propertyName: 'screen',
mapping: {
income: '#/components/schemas/IncomeInvestmentScreenDto',
events: '#/components/schemas/EventsInvestmentScreenDto',
operations: '#/components/schemas/TimestampInvestmentScreenDto',
},
},
};
expect(
document.paths['/api/v1/tbank-sync'].post?.requestBody,
).toMatchObject({
content: { 'application/json': { schema: screenSchema } },
});
expect(schemas?.TbankSyncAcceptedDto).toMatchObject({
properties: { scope: screenSchema },
});
expect(schemas?.TbankSyncStatusDto).toMatchObject({
properties: { scope: { ...screenSchema, nullable: true } },
});
for (const [model, screen] of [
['IncomeInvestmentScreenDto', 'income'],
['EventsInvestmentScreenDto', 'events'],
]) {
const schema = schemas?.[model];
assert(schema && !('$ref' in schema));
expect(schema.required).toContain('screen');
expect(schema.required).toContain('accountId');
expect(schema).toMatchObject({
properties: {
screen: { enum: [screen] },
from: { type: 'string', format: 'date' },
to: { type: 'string', format: 'date' },
},
});
}
expect(schemas?.TimestampInvestmentScreenDto).toMatchObject({
properties: {
from: { type: 'string', format: 'date-time' },
to: { type: 'string', format: 'date-time' },
},
});
expect(schemas).not.toHaveProperty('InvestmentScreenDto');
for (const scope of ['all', '{accountId}']) {
for (const [resource, format] of [
['income-analytics', 'date'],
['events', 'date'],
['operations', 'date-time'],
]) {
const parameters =
document.paths[`/api/v1/brokerage-accounts/${scope}/${resource}`].get
?.parameters;
for (const name of ['from', 'to'])
expect(
parameters?.find(
(parameter) => 'name' in parameter && parameter.name === name,
),
).toMatchObject({
name,
in: 'query',
schema: { format },
});
}
}
});
it('exposes required semantic income dependency roles', () => {
const schemas = createOpenApiDocument(app).components?.schemas;
expect(schemas?.IncomeAnalyticsResourceDto).toMatchObject({
properties: {
meta: { $ref: '#/components/schemas/IncomeAnalyticsMetaDto' },
},
});
const meta = schemas?.IncomeAnalyticsMetaDto;
assert(meta && !('$ref' in meta));
expect(meta.required).toContain('dependencies');
expect(meta).toMatchObject({
properties: {
dependencies: {
type: 'array',
items: { $ref: '#/components/schemas/IncomeDependencyDto' },
},
},
});
const dependency = schemas?.IncomeDependencyDto;
assert(dependency && !('$ref' in dependency));
expect(dependency.required).toContain('role');
expect(dependency).toMatchObject({
properties: {
role: { type: 'string', enum: ['selected', 'previous', 'year'] },
},
});
});
it('generates the runtime contract without server startup or deployment configuration', async () => {
const directory = await mkdtemp(resolve(tmpdir(), 'wealthledger-openapi-'));
try {