Files
WorthPath/apps/api/test/update-integration.test.ts
T

158 lines
5.7 KiB
TypeScript

import 'dotenv/config';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { randomUUID, randomBytes } from 'node:crypto';
import { PrismaClient } from '@prisma/client';
import { today } from '../src/validation';
import { readBackupZip } from '../src/zip';
const base = process.env.TEST_API_URL || 'http://127.0.0.1:3100/api';
test('real MySQL: groups, scheduled atomic execution, calendar, privacy and ZIP recovery', async () => {
const db = new PrismaClient(),
names: string[] = [];
const minute = (delta = 0) =>
new Date(Date.now() + delta + 8 * 3600000).toISOString().slice(0, 16);
async function call(path: string, cookie = '', method = 'GET', body?: unknown) {
const res = await fetch(base + path, {
method,
headers: {
Cookie: cookie,
Origin: process.env.WEB_ORIGIN === '*' ? 'http://localhost:5173' : process.env.WEB_ORIGIN!,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
return {
status: res.status,
data: res.headers.get('content-type')?.includes('application/zip')
? ((await readBackupZip(Buffer.from(await res.arrayBuffer()))) as any)
: await res.json(),
cookie: res.headers.get('set-cookie')?.split(';')[0] || '',
};
}
async function user() {
const username = 'wp_update_' + randomUUID(),
password = randomBytes(18).toString('hex');
names.push(username);
const result = await call('/auth/register', '', 'POST', { username, password });
assert.equal(result.status, 201);
return {
cookie: result.cookie,
password,
id: (await db.user.findUniqueOrThrow({ where: { username } })).id,
};
}
try {
const a = await user(),
b = await user();
const create = async (name: string, amount: string, groupName: string) => {
const result = await call('/positions', a.cookie, 'POST', {
name,
amount,
groupName,
kind: 'account',
side: 'asset',
category: 'bank',
currency: 'CNY',
date: '2026-09-01T10:00',
});
assert.equal(result.status, 201);
return result.data.id;
};
const sourceId = await create('Daily', '10', '日常'),
targetId = await create('Invest', '0', '理财');
assert.equal(
(await call('/positions', a.cookie)).data.find((p: any) => p.id === sourceId).groupName,
'日常',
);
const request = {
sourceId,
targetId,
amount: '25',
received: '25',
fee: '0',
date: minute(),
requestId: randomUUID(),
};
assert.equal((await call('/transfers', a.cookie, 'POST', request)).status, 201);
assert.equal(
(await call('/positions', a.cookie)).data.find((p: any) => p.id === sourceId).amount,
'-15',
);
const input = {
name: 'Rent',
operation: 'expense',
sourceId,
targetId: null,
amount: '2.5',
received: '0',
nextAt: minute(-60000),
intervalDays: 0,
notes: '',
};
const plan = await call('/schedules', a.cookie, 'POST', input);
assert.equal(plan.status, 201);
assert.equal((await call('/schedules', b.cookie)).data.length, 0);
assert.equal(
(await call('/schedules/' + plan.data.id, b.cookie, 'PATCH', { enabled: false })).status,
404,
);
const race = await Promise.all([
call('/schedules/run', a.cookie, 'POST', {}),
call('/schedules/run', a.cookie, 'POST', {}),
]);
assert.equal(
race.reduce((n, v) => n + v.data.executed, 0),
1,
);
assert.equal(
(await call('/positions', a.cookie)).data.find((p: any) => p.id === sourceId).amount,
'-17.5',
);
assert.equal(
(await call('/schedules/' + plan.data.id, a.cookie, 'PATCH', { enabled: true })).status,
400,
);
assert.equal((await call('/schedules/run', a.cookie, 'POST', {})).data.executed, 0);
const recurring = await call('/schedules', a.cookie, 'POST', {
...input,
name: 'Daily expense',
amount: '1',
intervalDays: 1,
nextAt: minute(-2 * 86400000),
});
assert.equal(recurring.status, 201);
for (let i = 0; i < 3; i++)
assert.equal((await call('/schedules/run', a.cookie, 'POST', {})).data.executed, 1);
assert.equal((await call('/schedules/run', a.cookie, 'POST', {})).data.executed, 0);
const calendar = await call('/calendar?month=' + today().slice(0, 7), a.cookie);
const day = calendar.data.items.find((v: any) => v.date === today());
assert.equal(day.expense, '5.50');
assert.equal(day.income, '0.00');
assert.equal((await call('/calendar/day?date=' + today(), a.cookie)).data.items.length, 4);
const backup = (await call('/backup', a.cookie)).data;
assert.equal(backup.schedules.length, 2);
assert.equal(
(await call('/backup/import', b.cookie, 'POST', { confirmed: true, backup })).status,
201,
);
assert.equal((await call('/schedules/run', b.cookie, 'POST', {})).data.executed, 0);
assert.deepEqual(
(await call('/positions', b.cookie)).data.map((v: any) => v.groupName).sort(),
['日常', '理财'].sort(),
);
const restored = (await call('/backup', b.cookie)).data;
assert.equal(restored.schedules.filter((p: any) => p.completed).length, 1);
await call('/positions/' + sourceId, a.cookie, 'PATCH', {
name: 'Daily',
category: 'bank',
groupName: '日常',
hidden: true,
});
assert.equal((await call('/schedules', a.cookie)).data.length, 0);
assert.equal((await call('/calendar/day?date=' + today(), a.cookie)).data.items.length, 0);
} finally {
await db.user.deleteMany({ where: { username: { in: names } } });
await db.$disconnect();
}
});