mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 06:56:35 +00:00
87 lines
3.4 KiB
TypeScript
87 lines
3.4 KiB
TypeScript
/**
|
|
* canAccessTrip is the shared trip-access check every domain reads its trip row
|
|
* from. It must return the trip's `currency`: the budget settlement takes its base
|
|
* currency straight off this row, and when the column was missing from the SELECT
|
|
* it silently fell back to 'EUR', inflating balances on every non-EUR trip that had
|
|
* a foreign-currency expense (#1543).
|
|
*/
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { db, canAccessTrip } from '../../../src/db/database';
|
|
import { CAN_ACCESS_TRIP_SQL, buildDbMock, createTestDb } from '../../helpers/test-db';
|
|
|
|
function seedUser(username: string): number {
|
|
return Number(
|
|
db.prepare("INSERT INTO users (username, email, password_hash, role) VALUES (?, ?, 'x', 'user')")
|
|
.run(username, `${username}@example.test`).lastInsertRowid,
|
|
);
|
|
}
|
|
|
|
describe('canAccessTrip', () => {
|
|
beforeEach(() => {
|
|
db.exec('DELETE FROM trip_members; DELETE FROM trips; DELETE FROM users;');
|
|
});
|
|
|
|
it('returns the trip currency for the owner (#1543)', async () => {
|
|
const owner = seedUser('owner');
|
|
const tripId = Number(
|
|
db.prepare("INSERT INTO trips (user_id, title, currency) VALUES (?, 'Trip', 'RUB')")
|
|
.run(owner).lastInsertRowid,
|
|
);
|
|
|
|
expect(await canAccessTrip(tripId, owner)).toMatchObject({ id: tripId, user_id: owner, currency: 'RUB' });
|
|
});
|
|
|
|
it('returns the trip currency for a member too', async () => {
|
|
const owner = seedUser('owner2');
|
|
const member = seedUser('member2');
|
|
const tripId = Number(
|
|
db.prepare("INSERT INTO trips (user_id, title, currency) VALUES (?, 'Trip', 'JPY')")
|
|
.run(owner).lastInsertRowid,
|
|
);
|
|
db.prepare('INSERT INTO trip_members (trip_id, user_id) VALUES (?, ?)').run(tripId, member);
|
|
|
|
expect(await canAccessTrip(tripId, member)).toMatchObject({ currency: 'JPY' });
|
|
});
|
|
|
|
it('returns undefined for a user with no access', async () => {
|
|
const owner = seedUser('owner3');
|
|
const stranger = seedUser('stranger3');
|
|
const tripId = Number(
|
|
db.prepare("INSERT INTO trips (user_id, title) VALUES (?, 'Trip')").run(owner).lastInsertRowid,
|
|
);
|
|
|
|
expect(await canAccessTrip(tripId, stranger)).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
/**
|
|
* The mock every domain test runs against had the pre-#1543 SELECT, so the very
|
|
* regression the block above pins was invisible to the suites that mock the db.
|
|
*/
|
|
describe('the buildDbMock stand-in for canAccessTrip', () => {
|
|
it('hands back the trip currency, like the real one', async () => {
|
|
const testDb = createTestDb();
|
|
try {
|
|
const owner = Number(
|
|
testDb.prepare("INSERT INTO users (username, email, password_hash, role) VALUES ('m', 'm@example.test', 'x', 'user')")
|
|
.run().lastInsertRowid,
|
|
);
|
|
const tripId = Number(
|
|
testDb.prepare("INSERT INTO trips (user_id, title, currency) VALUES (?, 'Trip', 'ISK')")
|
|
.run(owner).lastInsertRowid,
|
|
);
|
|
|
|
expect(await buildDbMock(testDb).canAccessTrip(tripId, owner)).toMatchObject({ id: tripId, currency: 'ISK' });
|
|
} finally {
|
|
testDb.close();
|
|
}
|
|
});
|
|
|
|
it('selects the same columns the production statement does', () => {
|
|
const columns = (sql: string) => sql.replace(/\s+/g, ' ').trim();
|
|
expect(columns(CAN_ACCESS_TRIP_SQL)).toBe(
|
|
'SELECT t.id, t.user_id, t.currency FROM trips t LEFT JOIN trip_members m ON m.trip_id = t.id AND m.user_id = ? WHERE t.id = ? AND (t.user_id = ? OR m.user_id IS NOT NULL)',
|
|
);
|
|
});
|
|
});
|