fix: harden auth and investment synchronization
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Successful in 11m9s
Conventional commits / commits (pull_request) Successful in 28s
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 10m38s

Resolve A01-A10 and C01-C07 from the backend audit.
Preserve auth failures, forced candle reconciliation and forecast freshness.
Validate MOEX responses and nested HTTP errors; remove unused legacy paths.

Verified: 209 unit tests, 262 SQLite e2e tests on Linux, OpenAPI and durability.
Record the remaining macOS native SQLite abort in the audit.
This commit is contained in:
2026-09-27 14:09:05 +03:00
parent 5d3835e796
commit 4796d9d3f0
55 changed files with 1954 additions and 569 deletions
@@ -116,7 +116,9 @@ describe('bank card calculations', () => {
amountKopecks: 5000n,
},
];
expect(bankSummary('2026-09', operations)).toEqual({
expect(
bankSummary({ from: '2026-09-01', to: '2026-09-30' }, operations),
).toEqual({
expenses: 120n,
refunds: 500n,
net: -380n,
@@ -125,7 +127,9 @@ describe('bank card calculations', () => {
{ categoryId: 'travel', expenses: 20n, refunds: 0n, net: 20n },
],
});
expect(bankSummary('2026-07', operations)).toEqual({
expect(
bankSummary({ from: '2026-07-01', to: '2026-07-31' }, operations),
).toEqual({
expenses: 0n,
refunds: 0n,
net: 0n,
@@ -141,7 +145,12 @@ describe('bank card calculations', () => {
amountKopecks: 2n ** 63n - 1n,
};
const total = 2n * (2n ** 63n - 1n);
expect(bankSummary('2026-09', [operation, operation])).toEqual({
expect(
bankSummary({ from: '2026-09-01', to: '2026-09-30' }, [
operation,
operation,
]),
).toEqual({
expenses: total,
refunds: 0n,
net: total,
@@ -1,6 +1,5 @@
import { BankOperationKind } from './bank-operation-kind';
const ISO_MONTH_LENGTH = 7;
export function moscowToday(now = new Date()): string {
return new Intl.DateTimeFormat('en-CA', {
timeZone: 'Europe/Moscow',
@@ -54,7 +53,7 @@ export function bankBalance(
}
export function bankSummary(
period: string | { from: string; to: string },
period: { from: string; to: string },
operations: Array<{
kind: string;
date: string;
@@ -70,12 +69,7 @@ export function bankSummary(
>();
// ponytail: O(N) history fold; optimize only after measuring growing histories.
for (const operation of operations) {
if (
typeof period === 'string'
? operation.date.slice(0, ISO_MONTH_LENGTH) !== period
: operation.date < period.from || operation.date > period.to
)
continue;
if (operation.date < period.from || operation.date > period.to) continue;
if (
operation.kind !== BankOperationKind.Expense &&
operation.kind !== BankOperationKind.Refund