Compare commits
2
Commits
main
...
e7827dcac6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7827dcac6 | ||
|
|
5cf914752d |
@@ -1,6 +1,9 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import type { Prisma } from '../generated/prisma/client';
|
||||
import type { PortfolioDto } from '../src/modules/investments/portfolio/presentation/portfolio.dto';
|
||||
|
||||
import { PortfolioRepository } from '../src/modules/investments/portfolio/infrastructure/portfolio.repository';
|
||||
import { PrismaService } from '../src/prisma/prisma.service';
|
||||
|
||||
@@ -16,9 +19,41 @@ describe('portfolio publication (SQLite)', () => {
|
||||
totalsByCurrency: [],
|
||||
positions: [],
|
||||
cashBalances: [],
|
||||
structure: {},
|
||||
} as never;
|
||||
structure: { currencies: [], unvaluedCategories: [] },
|
||||
} satisfies PortfolioDto;
|
||||
const accountId = 'account';
|
||||
const valuedSnapshot = {
|
||||
...snapshot,
|
||||
totalsByCurrency: [
|
||||
{ currency: 'RUB', value: '200' },
|
||||
{ currency: 'USD', value: '10' },
|
||||
],
|
||||
} satisfies PortfolioDto;
|
||||
const createRunningAttempt = () =>
|
||||
prisma.tbankSyncWork.create({
|
||||
data: {
|
||||
id: 'live',
|
||||
userId: 'owner',
|
||||
revision: 'current',
|
||||
scopeKey: `portfolio:${accountId}`,
|
||||
intent: {},
|
||||
state: 'running',
|
||||
attemptId: 'live-attempt',
|
||||
processRunId: 'run',
|
||||
priority: 1,
|
||||
progress: {},
|
||||
},
|
||||
});
|
||||
const interceptTransaction = (intercept: (tx: Prisma.TransactionClient) => void) => {
|
||||
const transaction = prisma.$transaction.bind(prisma);
|
||||
jest.spyOn(prisma, '$transaction').mockImplementation(
|
||||
(callback: (tx: Prisma.TransactionClient) => Promise<unknown>) =>
|
||||
transaction((tx) => {
|
||||
intercept(tx);
|
||||
return callback(tx);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await prisma.$connect();
|
||||
@@ -27,7 +62,9 @@ describe('portfolio publication (SQLite)', () => {
|
||||
});
|
||||
});
|
||||
afterAll(async () => prisma.$disconnect());
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
beforeEach(async () => {
|
||||
await prisma.tbankPortfolioValueHistory.deleteMany();
|
||||
await prisma.tbankConnection.deleteMany();
|
||||
await prisma.tbankConnection.create({
|
||||
data: {
|
||||
@@ -48,20 +85,7 @@ describe('portfolio publication (SQLite)', () => {
|
||||
});
|
||||
|
||||
it('publishes the same snapshot for the running attempt of the current revision', async () => {
|
||||
await prisma.tbankSyncWork.create({
|
||||
data: {
|
||||
id: 'live',
|
||||
userId: 'owner',
|
||||
revision: 'current',
|
||||
scopeKey: `portfolio:${accountId}`,
|
||||
intent: {},
|
||||
state: 'running',
|
||||
attemptId: 'live-attempt',
|
||||
processRunId: 'run',
|
||||
priority: 1,
|
||||
progress: {},
|
||||
},
|
||||
});
|
||||
await createRunningAttempt();
|
||||
const published = await repository.publish(
|
||||
{ id: 'live', attemptId: 'live-attempt', userId: 'owner', revision: 'current' },
|
||||
accountId,
|
||||
@@ -70,4 +94,99 @@ describe('portfolio publication (SQLite)', () => {
|
||||
assert.equal(published, true);
|
||||
expect(await prisma.tbankPortfolio.count()).toBe(1);
|
||||
});
|
||||
|
||||
it('rolls back the snapshot, first value point and claim when the second point fails', async () => {
|
||||
const previousFetchedAt = new Date('2025-12-31T00:00:00.000Z');
|
||||
const previousCapturedAt = new Date('2025-12-31T01:00:00.000Z');
|
||||
await prisma.tbankPortfolio.create({
|
||||
data: {
|
||||
userId: 'owner',
|
||||
revision: 'current',
|
||||
accountId,
|
||||
payload: snapshot,
|
||||
fetchedAt: previousFetchedAt,
|
||||
},
|
||||
});
|
||||
await prisma.tbankPortfolioValueHistory.create({
|
||||
data: {
|
||||
userId: 'owner',
|
||||
accountId,
|
||||
points: {
|
||||
create: {
|
||||
attemptId: 'previous-attempt',
|
||||
currency: 'RUB',
|
||||
value: '100',
|
||||
capturedAt: previousCapturedAt,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await createRunningAttempt();
|
||||
interceptTransaction((tx) => {
|
||||
const upsert = tx.tbankPortfolioValuePoint.upsert.bind(tx.tbankPortfolioValuePoint);
|
||||
jest
|
||||
.spyOn(tx.tbankPortfolioValuePoint, 'upsert')
|
||||
.mockImplementationOnce((args) => upsert(args))
|
||||
.mockRejectedValueOnce(new Error('injected second point failure'));
|
||||
});
|
||||
|
||||
await expect(
|
||||
repository.publish(
|
||||
{ id: 'live', attemptId: 'live-attempt', userId: 'owner', revision: 'current' },
|
||||
accountId,
|
||||
valuedSnapshot,
|
||||
),
|
||||
).rejects.toThrow('injected second point failure');
|
||||
|
||||
expect(
|
||||
await prisma.tbankPortfolio.findUnique({
|
||||
where: { userId_revision_accountId: { userId: 'owner', revision: 'current', accountId } },
|
||||
}),
|
||||
).toMatchObject({ payload: snapshot, fetchedAt: previousFetchedAt, dataRevision: 1 });
|
||||
expect(
|
||||
await prisma.tbankPortfolioValuePoint.findMany({
|
||||
select: { attemptId: true, value: true, capturedAt: true },
|
||||
}),
|
||||
).toEqual([
|
||||
{ attemptId: 'previous-attempt', value: '100', capturedAt: previousCapturedAt },
|
||||
]);
|
||||
expect(await prisma.tbankSyncWork.findUniqueOrThrow({ where: { id: 'live' } }))
|
||||
.toMatchObject({ state: 'running', attemptId: 'live-attempt' });
|
||||
});
|
||||
|
||||
it.each(['stale attempt', 'complete work', 'invalid token'] as const)(
|
||||
'does not publish for %s',
|
||||
async (scenario) => {
|
||||
await createRunningAttempt();
|
||||
if (scenario === 'complete work') {
|
||||
await prisma.tbankSyncWork.update({
|
||||
where: { id: 'live' },
|
||||
data: { state: 'complete' },
|
||||
});
|
||||
}
|
||||
if (scenario === 'invalid token') {
|
||||
await prisma.tbankConnection.update({
|
||||
where: { userId: 'owner' },
|
||||
data: { invalidToken: true },
|
||||
});
|
||||
}
|
||||
|
||||
await expect(
|
||||
repository.publish(
|
||||
{
|
||||
id: 'live',
|
||||
attemptId: scenario === 'stale attempt' ? 'stale-attempt' : 'live-attempt',
|
||||
userId: 'owner',
|
||||
revision: 'current',
|
||||
},
|
||||
accountId,
|
||||
valuedSnapshot,
|
||||
),
|
||||
).resolves.toBe(false);
|
||||
|
||||
expect(await prisma.tbankPortfolio.count()).toBe(0);
|
||||
expect(await prisma.tbankPortfolioValueHistory.count()).toBe(0);
|
||||
expect(await prisma.tbankPortfolioValuePoint.count()).toBe(0);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user