@accounter/scraper-app
Version:
Scraper app with Fastify server and React UI
134 lines (116 loc) • 3.88 kB
text/typescript
import { randomBytes } from 'node:crypto';
import { rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import Fastify, { type FastifyInstance } from 'fastify';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { defaultVault, saveVaultFile } from '../vault.js';
import { lockVault } from '../vault-store.js';
import { registerVaultRoutes } from '../vault-routes.js';
const PASSWORD = 'test-password-123';
let vaultPath: string;
let app: FastifyInstance;
beforeEach(async () => {
vaultPath = join(tmpdir(), `vault-test-${randomBytes(4).toString('hex')}.vault`);
process.env['VAULT_PATH'] = vaultPath;
const vault = defaultVault();
vault.accountRecords.push({
id: 'acc-1',
sourceId: 'src-a',
sourceType: 'poalim',
accountNumber: '123456',
branchNumber: '700',
status: 'pending',
});
await saveVaultFile(vaultPath, vault, PASSWORD);
app = Fastify();
await registerVaultRoutes(app);
await app.ready();
await app.inject({
method: 'POST',
url: '/api/vault/unlock',
payload: { password: PASSWORD },
});
});
afterEach(async () => {
lockVault();
await app.close();
await rm(vaultPath, { force: true });
delete process.env['VAULT_PATH'];
});
describe('GET /api/vault/accounts', () => {
it('returns the pre-seeded account', async () => {
const res = await app.inject({ method: 'GET', url: '/api/vault/accounts' });
expect(res.statusCode).toBe(200);
const list = res.json() as Array<{ id: string; status: string }>;
expect(list).toHaveLength(1);
expect(list[0].id).toBe('acc-1');
expect(list[0].status).toBe('pending');
});
it('returns 401 when vault is locked', async () => {
lockVault();
const res = await app.inject({ method: 'GET', url: '/api/vault/accounts' });
expect(res.statusCode).toBe(401);
});
});
describe('PUT /api/vault/accounts/:id', () => {
it('sets status to accepted and returns updated list', async () => {
const res = await app.inject({
method: 'PUT',
url: '/api/vault/accounts/acc-1',
payload: { status: 'accepted' },
});
expect(res.statusCode).toBe(200);
const list = res.json() as Array<{ id: string; status: string }>;
expect(list[0].status).toBe('accepted');
});
it('sets status to ignored', async () => {
const res = await app.inject({
method: 'PUT',
url: '/api/vault/accounts/acc-1',
payload: { status: 'ignored' },
});
expect(res.statusCode).toBe(200);
expect((res.json() as Array<{ status: string }>)[0].status).toBe('ignored');
});
it('persists across re-lock/unlock', async () => {
await app.inject({
method: 'PUT',
url: '/api/vault/accounts/acc-1',
payload: { status: 'accepted' },
});
lockVault();
await app.inject({
method: 'POST',
url: '/api/vault/unlock',
payload: { password: PASSWORD },
});
const res = await app.inject({ method: 'GET', url: '/api/vault/accounts' });
expect((res.json() as Array<{ status: string }>)[0].status).toBe('accepted');
});
it('returns 404 for unknown id', async () => {
const res = await app.inject({
method: 'PUT',
url: '/api/vault/accounts/does-not-exist',
payload: { status: 'accepted' },
});
expect(res.statusCode).toBe(404);
});
it('returns 400 for invalid status value', async () => {
const res = await app.inject({
method: 'PUT',
url: '/api/vault/accounts/acc-1',
payload: { status: 'pending' },
});
expect(res.statusCode).toBe(400);
});
it('returns 401 when vault is locked', async () => {
lockVault();
const res = await app.inject({
method: 'PUT',
url: '/api/vault/accounts/acc-1',
payload: { status: 'accepted' },
});
expect(res.statusCode).toBe(401);
});
});