mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-28 18:40:34 -05:00
[Maintenance] improve testing utils and add delete-user-file test (#421)
* improve testing utils and add delete-user-file test * remove linting errors * add release notes * match npm scripts naming style Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk> * add raw middleware for /sync --------- Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
This commit is contained in:
@@ -9,6 +9,10 @@
|
||||
"lint": "eslint . --max-warnings 0",
|
||||
"build": "tsc",
|
||||
"test": "NODE_ENV=test NODE_OPTIONS='--experimental-vm-modules --trace-warnings' jest --coverage",
|
||||
"db:migrate": "NODE_ENV=development node src/run-migrations.js up",
|
||||
"db:downgrade": "NODE_ENV=development node src/run-migrations.js down",
|
||||
"db:test-migrate": "NODE_ENV=test node src/run-migrations.js up",
|
||||
"db:test-downgrade": "NODE_ENV=test node src/run-migrations.js down",
|
||||
"types": "tsc --noEmit --incremental",
|
||||
"verify": "yarn lint && yarn types",
|
||||
"reset-password": "node src/scripts/reset-password.js",
|
||||
|
||||
@@ -13,8 +13,13 @@ import { SyncProtoBuf } from '@actual-app/crdt';
|
||||
|
||||
const app = express();
|
||||
app.use(errorMiddleware);
|
||||
app.use(express.json());
|
||||
app.use(express.raw({ type: 'application/actual-sync' }));
|
||||
|
||||
export { app as handlers };
|
||||
|
||||
const OK_RESPONSE = { status: 'ok' };
|
||||
|
||||
// This is a version representing the internal format of sync
|
||||
// messages. When this changes, all sync files need to be reset. We
|
||||
// will check this version when syncing and notify the user if they
|
||||
@@ -160,7 +165,7 @@ app.post('/user-create-key', (req, res) => {
|
||||
[keySalt, keyId, testContent, fileId],
|
||||
);
|
||||
|
||||
res.send(JSON.stringify({ status: 'ok' }));
|
||||
res.send(OK_RESPONSE);
|
||||
});
|
||||
|
||||
app.post('/reset-user-file', async (req, res) => {
|
||||
@@ -190,7 +195,7 @@ app.post('/reset-user-file', async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
res.send(JSON.stringify({ status: 'ok' }));
|
||||
res.send(OK_RESPONSE);
|
||||
});
|
||||
|
||||
app.post('/upload-user-file', async (req, res) => {
|
||||
@@ -333,7 +338,7 @@ app.post('/update-user-filename', (req, res) => {
|
||||
|
||||
accountDb.mutate('UPDATE files SET name = ? WHERE id = ?', [name, fileId]);
|
||||
|
||||
res.send(JSON.stringify({ status: 'ok' }));
|
||||
res.send(OK_RESPONSE);
|
||||
});
|
||||
|
||||
app.get('/list-user-files', (req, res) => {
|
||||
@@ -399,6 +404,20 @@ app.post('/delete-user-file', (req, res) => {
|
||||
let accountDb = getAccountDb();
|
||||
let { fileId } = req.body;
|
||||
|
||||
if (!fileId) {
|
||||
return res.status(422).send({
|
||||
details: 'fileId-required',
|
||||
reason: 'unprocessable-entity',
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
let rows = accountDb.all('SELECT * FROM files WHERE id = ?', [fileId]);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return res.status(400).send('file-not-found');
|
||||
}
|
||||
|
||||
accountDb.mutate('UPDATE files SET deleted = TRUE WHERE id = ?', [fileId]);
|
||||
res.send(JSON.stringify({ status: 'ok' }));
|
||||
res.send(OK_RESPONSE);
|
||||
});
|
||||
|
||||
@@ -75,3 +75,65 @@ describe('/download-user-file', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/delete-user-file', () => {
|
||||
it('returns 401 if the user is not authenticated', async () => {
|
||||
const res = await request(app).post('/delete-user-file');
|
||||
|
||||
expect(res.statusCode).toEqual(401);
|
||||
expect(res.body).toEqual({
|
||||
details: 'token-not-found',
|
||||
reason: 'unauthorized',
|
||||
status: 'error',
|
||||
});
|
||||
});
|
||||
|
||||
// it returns 422 if the fileId is not provided
|
||||
it('returns 422 if the fileId is not provided', async () => {
|
||||
const res = await request(app)
|
||||
.post('/delete-user-file')
|
||||
.set('x-actual-token', 'valid-token');
|
||||
|
||||
expect(res.statusCode).toEqual(422);
|
||||
expect(res.body).toEqual({
|
||||
details: 'fileId-required',
|
||||
reason: 'unprocessable-entity',
|
||||
status: 'error',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 400 if the file does not exist', async () => {
|
||||
const res = await request(app)
|
||||
.post('/delete-user-file')
|
||||
.set('x-actual-token', 'valid-token')
|
||||
.send({ fileId: 'non-existing-file-id' });
|
||||
|
||||
expect(res.statusCode).toEqual(400);
|
||||
expect(res.text).toEqual('file-not-found');
|
||||
});
|
||||
|
||||
it('marks the file as deleted', async () => {
|
||||
const accountDb = getAccountDb();
|
||||
const fileId = 'existing-file-id';
|
||||
|
||||
// Insert a file into the database
|
||||
accountDb.mutate(
|
||||
'INSERT OR IGNORE INTO files (id, deleted) VALUES (?, FALSE)',
|
||||
[fileId],
|
||||
);
|
||||
|
||||
const res = await request(app)
|
||||
.post('/delete-user-file')
|
||||
.set('x-actual-token', 'valid-token')
|
||||
.send({ fileId });
|
||||
|
||||
expect(res.statusCode).toEqual(200);
|
||||
expect(res.body).toEqual({ status: 'ok' });
|
||||
|
||||
// Verify that the file is marked as deleted
|
||||
const rows = accountDb.all('SELECT deleted FROM files WHERE id = ?', [
|
||||
fileId,
|
||||
]);
|
||||
expect(rows[0].deleted).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
8
src/run-migrations.js
Normal file
8
src/run-migrations.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import run from './migrations.js';
|
||||
|
||||
const direction = process.argv[2] || 'up';
|
||||
|
||||
run(direction).catch((err) => {
|
||||
console.error('Migration failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
6
upcoming-release-notes/421.md
Normal file
6
upcoming-release-notes/421.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [tcrasset]
|
||||
---
|
||||
|
||||
Improve testing utils and add delete-user-file test
|
||||
Reference in New Issue
Block a user