perf: optimize portfolio queries and workflows
This commit is contained in:
1 parent
da98d02661
commit
a7e0a0fe58
45 files changed
+4446
-637
No files matched your search
@@ -0,0 +1,173 @@
|
||||
import 'dotenv/config';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { randomUUID, randomBytes, createHash } from 'node:crypto';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { readBackupZip, packBackup } from '../src/zip';
|
||||
const base = process.env.TEST_API_URL || 'http://127.0.0.1:3100/api';
|
||||
const origin = process.env.WEB_ORIGIN === '*' ? 'http://localhost:5173' : process.env.WEB_ORIGIN!;
|
||||
test('borrow/lend/collect/repay pair balances atomically and survive concurrency and restore', async () => {
|
||||
const db = new PrismaClient(),
|
||||
users: string[] = [];
|
||||
async function user() {
|
||||
const row = await db.user.create({
|
||||
data: { username: 'wp_debt_' + randomUUID(), passwordHash: 'not-a-login-hash' },
|
||||
});
|
||||
users.push(row.id);
|
||||
const token = randomBytes(32).toString('hex');
|
||||
await db.session.create({
|
||||
data: {
|
||||
id: createHash('sha256').update(token).digest('hex'),
|
||||
userId: row.id,
|
||||
expiresAt: new Date(Date.now() + 600000),
|
||||
},
|
||||
});
|
||||
return { id: row.id, token };
|
||||
}
|
||||
async function call(token: string, path: string, method = 'GET', body?: unknown) {
|
||||
const response = await fetch(base + path, {
|
||||
method,
|
||||
headers: {
|
||||
Cookie: 'wp_session=' + token,
|
||||
Origin: origin,
|
||||
...(body ? { 'Content-Type': 'application/json' } : {}),
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
return {
|
||||
status: response.status,
|
||||
data: response.headers.get('content-type')?.includes('application/zip')
|
||||
? ((await readBackupZip(Buffer.from(await response.arrayBuffer()))) as any)
|
||||
: await response.json(),
|
||||
};
|
||||
}
|
||||
try {
|
||||
const a = await user(),
|
||||
b = await user();
|
||||
async function position(name: string, kind: string, side: string, amount = '0') {
|
||||
const result = await call(a.token, '/positions', 'POST', {
|
||||
name,
|
||||
kind,
|
||||
side,
|
||||
amount,
|
||||
currency: 'CNY',
|
||||
category: kind === 'debt' ? 'personal' : 'bank',
|
||||
date: '2026-09-01T00:00',
|
||||
});
|
||||
assert.equal(result.status, 201);
|
||||
return result.data.id as string;
|
||||
}
|
||||
const cash = await position('test account', 'account', 'asset', '1000'),
|
||||
borrowed = await position('test borrowed', 'debt', 'liability'),
|
||||
lent = await position('test lent', 'debt', 'asset');
|
||||
const request = (operation: string, targetId: string, amount: string, fee = '0') => ({
|
||||
requestId: randomUUID(),
|
||||
operation,
|
||||
sourceId: cash,
|
||||
targetId,
|
||||
amount,
|
||||
received: amount,
|
||||
fee,
|
||||
date: '2026-09-02T10:00',
|
||||
});
|
||||
const borrow = request('borrow', borrowed, '100', '2');
|
||||
const parallel = await Promise.all([
|
||||
call(a.token, '/transfers', 'POST', borrow),
|
||||
call(a.token, '/transfers', 'POST', borrow),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
parallel.map((r) => r.status),
|
||||
[201, 201],
|
||||
);
|
||||
assert.equal((await call(a.token, '/overview')).data.net, '998.00');
|
||||
assert.equal(
|
||||
(await call(a.token, '/transfers', 'POST', request('lend', lent, '200', '-2'))).status,
|
||||
201,
|
||||
);
|
||||
assert.equal((await call(a.token, '/overview')).data.net, '1000.00');
|
||||
assert.equal(
|
||||
(await call(a.token, '/transfers', 'POST', request('collect', lent, '50'))).status,
|
||||
201,
|
||||
);
|
||||
assert.equal(
|
||||
(await call(a.token, '/transfers', 'POST', request('repay', borrowed, '30', '1'))).status,
|
||||
201,
|
||||
);
|
||||
assert.equal((await call(a.token, '/overview')).data.net, '999.00');
|
||||
const list = (await call(a.token, '/positions')).data;
|
||||
assert.equal(list.find((p: any) => p.id === cash).amount, '919');
|
||||
assert.equal(list.find((p: any) => p.id === borrowed).amount, '70');
|
||||
assert.equal(list.find((p: any) => p.id === lent).amount, '150');
|
||||
assert.equal(list.find((p: any) => p.id === lent).outgoing[0].targetId, cash);
|
||||
const count = await db.revision.count({ where: { position: { userId: a.id } } });
|
||||
assert.equal(
|
||||
(await call(a.token, '/transfers', 'POST', request('repay', borrowed, '71'))).status,
|
||||
400,
|
||||
);
|
||||
assert.equal(
|
||||
(await call(a.token, '/transfers', 'POST', request('lend', borrowed, '10'))).status,
|
||||
400,
|
||||
);
|
||||
assert.equal(
|
||||
(await call(b.token, '/transfers', 'POST', request('repay', borrowed, '10'))).status,
|
||||
400,
|
||||
);
|
||||
assert.equal(await db.revision.count({ where: { position: { userId: a.id } } }), count);
|
||||
const race = await Promise.all(
|
||||
[1, 2].map(() => call(a.token, '/transfers', 'POST', request('collect', lent, '100'))),
|
||||
);
|
||||
assert.equal(race.filter((r) => r.status === 201).length, 1, JSON.stringify(race));
|
||||
assert.equal(race.filter((r) => r.status === 400 || r.status === 409).length, 1);
|
||||
const paired = (await call(a.token, '/positions/' + cash + '/history')).data.items.find(
|
||||
(r: any) => r.reason === 'loan_collect',
|
||||
);
|
||||
assert.equal(
|
||||
(
|
||||
await call(a.token, '/positions/' + cash + '/revisions/' + paired.id, 'PUT', {
|
||||
amount: '1',
|
||||
date: '2026-09-02T10:00',
|
||||
})
|
||||
).status,
|
||||
409,
|
||||
);
|
||||
// A later record at the same business minute can be corrected by sequence.
|
||||
assert.equal(
|
||||
(
|
||||
await call(a.token, '/positions/' + cash + '/revisions', 'POST', {
|
||||
amount: '1020',
|
||||
date: '2026-09-02T10:00',
|
||||
})
|
||||
).status,
|
||||
201,
|
||||
);
|
||||
const latest = (await call(a.token, '/positions/' + cash + '/history?limit=1')).data.items[0];
|
||||
assert.equal(
|
||||
(
|
||||
await call(a.token, '/positions/' + cash + '/revisions/' + latest.id, 'PUT', {
|
||||
amount: '1021',
|
||||
date: '2026-09-02T10:00',
|
||||
})
|
||||
).status,
|
||||
200,
|
||||
);
|
||||
const backup = (await call(a.token, '/backup')).data;
|
||||
assert.equal(JSON.parse(packBackup(backup)['manifest.json']).version, 6);
|
||||
assert.equal(backup.transfers.length, 5);
|
||||
assert.ok(backup.transfers.some((p: any) => p.operation === 'lend' && p.fee === '-2'));
|
||||
assert.equal(
|
||||
(await call(b.token, '/backup/import', 'POST', { confirmed: true, backup })).status,
|
||||
201,
|
||||
);
|
||||
assert.equal(
|
||||
(await call(b.token, '/overview')).data.net,
|
||||
(await call(a.token, '/overview')).data.net,
|
||||
);
|
||||
assert.equal(await db.transfer.count({ where: { userId: b.id } }), 5);
|
||||
const restored = (await call(b.token, '/positions?kind=debt')).data;
|
||||
assert.equal(restored.find((p: any) => p.side === 'asset').amount, '50');
|
||||
assert.equal(restored.find((p: any) => p.side === 'liability').amount, '70');
|
||||
} finally {
|
||||
await db.user.deleteMany({ where: { id: { in: users } } });
|
||||
await db.$disconnect();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user