fix: surface durable hourly attempt in accounts and income meta (WealthLedger/backend#58)
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 17s
Backend verify / verify (push) Failing after 7m31s

This commit was merged in pull request #92.
This commit is contained in:
2026-10-14 18:00:20 +03:00
parent b90cedd580
commit 14bcb9da5f
6 changed files with 80 additions and 4 deletions
+3 -1
View File
@@ -266,7 +266,9 @@ describe('local income completeness and exact money (SQLite)', () => {
await repo.fail(fence, false);
const after = await read();
expect(after.data).toEqual(before.data);
expect(after.meta.lastAttempt.state).toBe('failed');
// Without an hourly walk the durable attempt is absent; the failed
// source work stays a technical observation, not the hourly attempt.
expect(after.meta.lastAttempt.state).toBe('none');
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
});
it('retains current-month observed completeness and does not renew source timestamps', async () => {
+69
View File
@@ -15,6 +15,7 @@ import {
} from '../src/integrations/tbank/tbank.client';
import { configureApp } from '../src/main';
import { BrokerageAccountsResourceDto } from '../src/modules/investments/brokerage-accounts/brokerage-account.dto';
import { IncomeAnalyticsResourceDto } from '../src/modules/investments/income/income-analytics.dto';
import { PortfolioService } from '../src/modules/investments/portfolio/application/portfolio.service';
import { PortfolioResourceDto } from '../src/modules/investments/portfolio/presentation/portfolio.dto';
import { PrismaService } from '../src/prisma/prisma.service';
@@ -325,6 +326,74 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
expect(upstream.getPortfolio).toHaveBeenCalledTimes(1);
});
it('reports the durable hourly attempt in accounts and income meta with section relevance', async () => {
await put().expect(200);
await seedMembership();
await requestPortfolio('account');
await settled();
const connection = await prisma.tbankConnection.findUniqueOrThrow({
where: { userId: 'owner' },
});
const startedAt = new Date('2026-10-10T10:00:00.000Z');
const finishedAt = new Date('2026-10-10T10:05:00.000Z');
await prisma.tbankSyncWork.create({
data: {
id: 'hourly-attempt',
userId: 'owner',
revision: connection.revision,
scopeKey: 'hourly',
intent: {},
state: 'complete',
attemptId: 'hourly-attempt',
processRunId: 'test',
priority: 0,
progress: {
result: 'partial',
sections: [
{
section: 'portfolio',
accountId: 'account',
errorCode: 'TBANK_UNAVAILABLE',
},
{
section: 'operations',
accountId: 'account',
errorCode: 'TBANK_UNAVAILABLE',
},
],
},
createdAt: startedAt,
updatedAt: finishedAt,
},
});
const accounts = await get('brokerage-accounts').expect(200);
const accountsMeta = (accounts.body as BrokerageAccountsResourceDto).meta;
expect(accountsMeta.lastAttempt.state).toBe('partial');
expect(accountsMeta.lastAttempt.startedAt).toBe(startedAt.toISOString());
expect(accountsMeta.lastAttempt.finishedAt).toBe(finishedAt.toISOString());
expect(accountsMeta.lastAttempt.problems).toEqual([
{
section: 'portfolio',
accountId: 'account',
errorCode: 'TBANK_UNAVAILABLE',
},
]);
const income = await get(
'brokerage-accounts/account/income-analytics?sync=observe',
).expect(200);
const incomeMeta = (income.body as IncomeAnalyticsResourceDto).meta;
expect(incomeMeta.lastAttempt.state).toBe('partial');
expect(incomeMeta.lastAttempt.startedAt).toBe(startedAt.toISOString());
expect(incomeMeta.lastAttempt.finishedAt).toBe(finishedAt.toISOString());
expect(incomeMeta.lastAttempt.problems).toEqual([
{
section: 'operations',
accountId: 'account',
errorCode: 'TBANK_UNAVAILABLE',
},
]);
});
it('recovers abandoned discovery only on a data request and coalesces concurrent reads', async () => {
await put().expect(200);
const connection = await prisma.tbankConnection.update({