mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
test(server): seed the files MCP and roadtrip e2e specs through the factories
Converts the raw prepare() fixtures and reads in the files MCP spec and the roadtrip e2e to the ORM factories and readers. The files spec seeds its trip files through makeTripFile and its links through linkFile, keeping the helper's explicit-null handling for size and type. The roadtrip e2e keeps its pinned trip, day and place ids, which its routes address directly, now written through insertRow and insertRows.
This commit is contained in:
@@ -39,7 +39,9 @@ import type { Server } from 'http';
|
||||
import request from 'supertest';
|
||||
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type MockInstance } from 'vitest';
|
||||
import { TestUnitOfWorkModule } from '../helpers/test-uow';
|
||||
import { createTestMikroOrmModule } from '../helpers/test-orm';
|
||||
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
|
||||
import { countRows, deleteRows, findRow, insertRow, insertRows, updateRows } from '../helpers/factories/rows';
|
||||
import { makeUser } from '../helpers/factories/users';
|
||||
|
||||
vi.mock('../../src/db/database', async () => {
|
||||
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
|
||||
@@ -48,14 +50,24 @@ vi.mock('../../src/db/database', async () => {
|
||||
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn() }));
|
||||
|
||||
import { db } from '../../src/db/database';
|
||||
import { Addons } from '../../src/db/entities/Addons.entity';
|
||||
import { Days } from '../../src/db/entities/Days.entity';
|
||||
import { Places } from '../../src/db/entities/Places.entity';
|
||||
import { RoadtripDayTracks } from '../../src/db/entities/RoadtripDayTracks.entity';
|
||||
import { RoadtripPreferences } from '../../src/db/entities/RoadtripPreferences.entity';
|
||||
import { RoadtripVias } from '../../src/db/entities/RoadtripVias.entity';
|
||||
import { TripMembers } from '../../src/db/entities/TripMembers.entity';
|
||||
import { Trips } from '../../src/db/entities/Trips.entity';
|
||||
|
||||
let orm: TestOrm;
|
||||
|
||||
const ADDON_ID = 'roadtrip';
|
||||
|
||||
function setAddon(enabled: boolean): void {
|
||||
async function setAddon(enabled: boolean): Promise<void> {
|
||||
// The seeder (`AddonSeeder`, run once as part of `createSnapshotTestDb()`'s
|
||||
// migration pass) already inserted this row, disabled — a plain UPDATE,
|
||||
// not an upsert, matching every other addon-toggling e2e in this file set.
|
||||
db.prepare('UPDATE addons SET enabled = ? WHERE id = ?').run(enabled ? 1 : 0, ADDON_ID);
|
||||
await updateRows(orm, Addons, { id: ADDON_ID }, { enabled });
|
||||
}
|
||||
|
||||
describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
@@ -76,35 +88,39 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
// harness.ts's seedUser() omits password_hash, which the real migrated
|
||||
// schema requires NOT NULL (days.e2e.test.ts/assignments.e2e.test.ts's
|
||||
// own precedent) — raw inserts here instead, matching the SeededUser
|
||||
// shape id/role/password_version=0 that sessionCookie() needs.
|
||||
db.prepare("INSERT INTO users (id, username, email, password_hash, role, password_version) VALUES (1, 'e2e-user', 'e2e@example.test', 'x', 'user', 0)").run();
|
||||
db.prepare("INSERT INTO users (id, username, email, password_hash, role, password_version) VALUES (2, 'other', 'other@example.test', 'x', 'user', 0)").run();
|
||||
db.prepare('INSERT INTO trips (id, user_id, title) VALUES (5, 1, ?)').run('Norway');
|
||||
db.prepare('INSERT INTO trips (id, user_id, title) VALUES (6, 2, ?)').run('Somebody else');
|
||||
db.prepare('INSERT INTO days (id, trip_id, day_number) VALUES (3, 5, 1)').run();
|
||||
db.prepare('INSERT INTO days (id, trip_id, day_number) VALUES (4, 6, 1)').run();
|
||||
orm = await createTestOrm(db);
|
||||
// Pinned ids: sessionCookie(1) and (2) sign for exactly these users, and
|
||||
// the routes below address the trips, days and places by their ids.
|
||||
await makeUser(orm, { id: 1, username: 'e2e-user', email: 'e2e@example.test' });
|
||||
await makeUser(orm, { id: 2, username: 'other', email: 'other@example.test' });
|
||||
await insertRows(orm, Trips, [
|
||||
{ id: 5, user: 1, title: 'Norway' },
|
||||
{ id: 6, user: 2, title: 'Somebody else' },
|
||||
]);
|
||||
await insertRows(orm, Days, [
|
||||
{ id: 3, trip: 5, day_number: 1 },
|
||||
{ id: 4, trip: 6, day_number: 1 },
|
||||
]);
|
||||
// A track (a place carrying a route geometry) on each trip.
|
||||
db.prepare("INSERT INTO places (id, trip_id, name, route_geometry) VALUES (10, 5, 'Scenic', '[[1,2]]')").run();
|
||||
db.prepare("INSERT INTO places (id, trip_id, name, route_geometry) VALUES (11, 6, 'Theirs', '[[1,2]]')").run();
|
||||
await insertRow(orm, Places, { id: 10, trip: 5, name: 'Scenic', route_geometry: '[[1,2]]' });
|
||||
await insertRow(orm, Places, { id: 11, trip: 6, name: 'Theirs', route_geometry: '[[1,2]]' });
|
||||
// An ordinary place, which is not a track.
|
||||
db.prepare("INSERT INTO places (id, trip_id, name) VALUES (12, 5, 'Just a stop')").run();
|
||||
await insertRow(orm, Places, { id: 12, trip: 5, name: 'Just a stop' });
|
||||
app = await build();
|
||||
checkPermission = vi.spyOn(app.get(PermissionsService), 'checkPermission');
|
||||
server = app.getHttpServer();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
db.prepare('DELETE FROM roadtrip_vias').run();
|
||||
db.prepare('DELETE FROM roadtrip_day_tracks').run();
|
||||
beforeEach(async () => {
|
||||
await deleteRows(orm, RoadtripVias);
|
||||
await deleteRows(orm, RoadtripDayTracks);
|
||||
checkPermission.mockReturnValue(true);
|
||||
setAddon(true);
|
||||
await setAddon(true);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app?.close();
|
||||
await orm.close();
|
||||
});
|
||||
|
||||
const cookie = () => sessionCookie(1);
|
||||
@@ -115,7 +131,7 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
await request(server).get('/api/trips/6/roadtrip/charging/10').set('Cookie', cookie()).expect(404);
|
||||
await request(server).get('/api/trips/5/roadtrip/charging/10').set('Cookie', cookie()).expect(200);
|
||||
expect(read).toHaveBeenCalledWith(5, 10);
|
||||
setAddon(false);
|
||||
await setAddon(false);
|
||||
await request(server).get('/api/trips/5/roadtrip/charging/10').set('Cookie', cookie()).expect(404);
|
||||
expect(read).toHaveBeenCalledTimes(1);
|
||||
} finally { read.mockRestore(); }
|
||||
@@ -146,7 +162,7 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
expect(lookup).not.toHaveBeenCalled();
|
||||
await post().send(body).expect(200);
|
||||
expect(lookup).toHaveBeenCalledWith(48.137, 11.575, 'Ladepark Nord');
|
||||
setAddon(false);
|
||||
await setAddon(false);
|
||||
await post().send(body).expect(404);
|
||||
expect(lookup).toHaveBeenCalledTimes(1);
|
||||
} finally { lookup.mockRestore(); }
|
||||
@@ -164,7 +180,7 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
await request(server).post('/api/trips/5/roadtrip/google-maps-import').set('Cookie', cookie()).send({ ...input, stops: [{ name: 'A', lat: 999, lng: 0 }] }).expect(400);
|
||||
await request(server).post('/api/trips/5/roadtrip/google-maps-import').set('Cookie', cookie()).send(input).expect(200);
|
||||
expect(save).toHaveBeenCalledWith(5, 1, input, undefined);
|
||||
setAddon(false);
|
||||
await setAddon(false);
|
||||
await request(server).post('/api/trips/5/roadtrip/google-maps-import').set('Cookie', cookie()).send(input).expect(404);
|
||||
await request(server).post('/api/roadtrip/google-maps-preview').set('Cookie', cookie()).send({ url: 'https://google.com/maps/dir/A/B' }).expect(404);
|
||||
expect(save).toHaveBeenCalledTimes(1);
|
||||
@@ -179,7 +195,7 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
await request(server).post('/api/roadtrip/search-area').set('Cookie', cookie()).send({ ...input, categories: ['invalid'] }).expect(400);
|
||||
await request(server).post('/api/roadtrip/search-area').set('Cookie', cookie()).send(input).expect(200);
|
||||
expect(search).toHaveBeenCalledWith(input, 1);
|
||||
setAddon(false);
|
||||
await setAddon(false);
|
||||
await request(server).post('/api/roadtrip/search-area').set('Cookie', cookie()).send(input).expect(404);
|
||||
expect(search).toHaveBeenCalledTimes(1);
|
||||
} finally { search.mockRestore(); }
|
||||
@@ -190,7 +206,7 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
await request(server).get('/api/trips/5/roadtrip/hazards').expect(401);
|
||||
await request(server).get('/api/trips/6/roadtrip/hazards').set('Cookie', cookie()).expect(404);
|
||||
await request(server).get('/api/trips/5/roadtrip/hazards').set('Cookie', cookie()).expect(200);
|
||||
setAddon(false);
|
||||
await setAddon(false);
|
||||
await request(server).get('/api/trips/5/roadtrip/hazards').set('Cookie', cookie()).expect(404);
|
||||
expect(read).toHaveBeenCalledTimes(1);
|
||||
} finally { read.mockRestore(); }
|
||||
@@ -198,7 +214,7 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
|
||||
describe('shared trip driving preferences', () => {
|
||||
it('shares values with members and isolates other trips', async () => {
|
||||
db.prepare('INSERT INTO trip_members (trip_id, user_id) VALUES (5, 2)').run();
|
||||
await insertRow(orm, TripMembers, { trip: 5, user: 2 });
|
||||
try {
|
||||
await request(server)
|
||||
.put('/api/trips/5/roadtrip/preferences')
|
||||
@@ -232,14 +248,14 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
.send({ roadtrip_range_km: 999 })
|
||||
.expect(403);
|
||||
expect(
|
||||
db.prepare("SELECT value FROM roadtrip_preferences WHERE trip_id = 5 AND key = 'roadtrip_range_km'").get(),
|
||||
).toEqual({ value: '160' });
|
||||
(await findRow(orm, RoadtripPreferences, { trip: 5, key: 'roadtrip_range_km' }))?.value,
|
||||
).toBe('160');
|
||||
} finally {
|
||||
db.prepare('DELETE FROM trip_members WHERE trip_id = 5 AND user_id = 2').run();
|
||||
await deleteRows(orm, TripMembers, { trip: 5, user: 2 });
|
||||
}
|
||||
});
|
||||
it('ROADTRIP-E2E-013: the stay switch is off until it is set, and round-trips as a boolean', async () => {
|
||||
db.prepare("DELETE FROM roadtrip_preferences WHERE trip_id = 5 AND key = 'roadtrip_hotel_bookends'").run();
|
||||
await deleteRows(orm, RoadtripPreferences, { trip: 5, key: 'roadtrip_hotel_bookends' });
|
||||
const before = await request(server).get('/api/trips/5/roadtrip/preferences').set('Cookie', cookie()).expect(200);
|
||||
expect(before.body.preferences).not.toHaveProperty('roadtrip_hotel_bookends');
|
||||
|
||||
@@ -258,11 +274,11 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
.send({ roadtrip_hotel_bookends: false })
|
||||
.expect(200);
|
||||
expect(
|
||||
db.prepare("SELECT value FROM roadtrip_preferences WHERE trip_id = 5 AND key = 'roadtrip_hotel_bookends'").get(),
|
||||
).toEqual({ value: 'false' });
|
||||
(await findRow(orm, RoadtripPreferences, { trip: 5, key: 'roadtrip_hotel_bookends' }))?.value,
|
||||
).toBe('false');
|
||||
});
|
||||
it('ROADTRIP-E2E-014: a stay switch that is not a boolean is a 400, not a stored row', async () => {
|
||||
db.prepare("DELETE FROM roadtrip_preferences WHERE trip_id = 5 AND key = 'roadtrip_hotel_bookends'").run();
|
||||
await deleteRows(orm, RoadtripPreferences, { trip: 5, key: 'roadtrip_hotel_bookends' });
|
||||
for (const value of ['true', 1, null]) {
|
||||
await request(server)
|
||||
.put('/api/trips/5/roadtrip/preferences')
|
||||
@@ -271,8 +287,8 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
.expect(400);
|
||||
}
|
||||
expect(
|
||||
db.prepare("SELECT value FROM roadtrip_preferences WHERE trip_id = 5 AND key = 'roadtrip_hotel_bookends'").get(),
|
||||
).toBeUndefined();
|
||||
(await findRow(orm, RoadtripPreferences, { trip: 5, key: 'roadtrip_hotel_bookends' })),
|
||||
).toBeNull();
|
||||
});
|
||||
it('refuses strangers and invalid daily windows', async () => {
|
||||
await request(server).get('/api/trips/5/roadtrip/preferences').set('Cookie', sessionCookie(2)).expect(404);
|
||||
@@ -300,7 +316,7 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
];
|
||||
|
||||
it('ROADTRIP-E2E-001: answers 404 on every route while the addon is off', async () => {
|
||||
setAddon(false);
|
||||
await setAddon(false);
|
||||
for (const [method, url, body] of routes) {
|
||||
const req = (request(server) as never as Record<string, (u: string) => request.Test>)
|
||||
[method](url)
|
||||
@@ -313,7 +329,7 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
it('ROADTRIP-E2E-002: a disabled addon owes an anonymous caller a 404, not a 401', async () => {
|
||||
// The gate leads the chain for this reason: a 401 tells a stranger the
|
||||
// route exists. Both answers refuse; only one of them says nothing.
|
||||
setAddon(false);
|
||||
await setAddon(false);
|
||||
const res = await request(server).get('/api/trips/5/roadtrip/vias');
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
@@ -465,7 +481,7 @@ describe('Roadtrip e2e (real guard chain + temp SQLite)', () => {
|
||||
.set('Cookie', cookie())
|
||||
.send({ after_order_index: 0, lat: 999, lng: 10 });
|
||||
expect(res.status).toBe(400);
|
||||
expect(db.prepare('SELECT COUNT(*) c FROM roadtrip_vias').get()).toEqual({ c: 0 });
|
||||
expect(await countRows(orm, RoadtripVias)).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,6 +44,13 @@ import { invalidatePermissionsCache } from '../../../src/nest/permissions/permis
|
||||
import { createUser, createTrip, createDay, createPlace, createDayAssignment, createReservation, addTripMember } from '../../helpers/factories';
|
||||
import { createMcpHarness, parseToolResult, type McpHarness } from '../../helpers/mcp-harness';
|
||||
import { expectRegisteredProvider } from '../../helpers/module-providers';
|
||||
import { createTestOrm, type TestOrm } from '../../helpers/test-orm';
|
||||
import { countRows, findRow, findRows, updateRows } from '../../helpers/factories/rows';
|
||||
import { setAppSetting } from '../../helpers/factories/settings';
|
||||
import { linkFile, makeTripFile } from '../../helpers/factories/files';
|
||||
import { AppSettings } from '../../../src/db/entities/AppSettings.entity';
|
||||
import { FileLinks } from '../../../src/db/entities/FileLinks.entity';
|
||||
import { TripFiles } from '../../../src/db/entities/TripFiles.entity';
|
||||
import { FilesModule } from '../../../src/nest/files/files.module';
|
||||
import { FilesMcp } from '../../../src/nest/files/files.mcp';
|
||||
import { FILE_CONTENT_MAX } from '../../../src/nest/files/files.service';
|
||||
@@ -58,7 +65,14 @@ beforeEach(async () => {
|
||||
await invalidatePermissionsCache();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
let orm: TestOrm;
|
||||
|
||||
beforeAll(async () => {
|
||||
orm = await createTestOrm(testDb);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await orm.close();
|
||||
testDb.close();
|
||||
});
|
||||
|
||||
@@ -69,7 +83,7 @@ async function withHarness(userId: number, fn: (h: McpHarness) => Promise<void>)
|
||||
|
||||
/** Lower a configurable action the way the admin permission panel does. */
|
||||
async function setPermission(action: string, level: string): Promise<void> {
|
||||
testDb.prepare('INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)').run(`perm_${action}`, level);
|
||||
await setAppSetting(orm, `perm_${action}`, level);
|
||||
await invalidatePermissionsCache();
|
||||
}
|
||||
|
||||
@@ -88,25 +102,20 @@ interface FileRowOverrides {
|
||||
|
||||
/** A trip_files row, straight in, so each case controls every column. */
|
||||
function insertFile(tripId: number, overrides: Partial<FileRowOverrides> = {}) {
|
||||
const info = testDb.prepare(`
|
||||
INSERT INTO trip_files (trip_id, filename, original_name, file_size, mime_type, description, uploaded_by, place_id, reservation_id, starred, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
tripId,
|
||||
overrides.filename ?? 'stored-visa.pdf',
|
||||
overrides.original_name ?? 'visa.pdf',
|
||||
return makeTripFile(orm, tripId, {
|
||||
filename: overrides.filename ?? 'stored-visa.pdf',
|
||||
original_name: overrides.original_name ?? 'visa.pdf',
|
||||
// `??` would swallow an explicit null here, and a row with no recorded size
|
||||
// or type is exactly what the fallback cases need.
|
||||
overrides.file_size === undefined ? 2 : overrides.file_size,
|
||||
overrides.mime_type === undefined ? 'application/pdf' : overrides.mime_type,
|
||||
overrides.description ?? null,
|
||||
overrides.uploaded_by ?? null,
|
||||
overrides.place_id ?? null,
|
||||
overrides.reservation_id ?? null,
|
||||
overrides.starred ?? 0,
|
||||
overrides.deleted_at ?? null,
|
||||
);
|
||||
return testDb.prepare('SELECT * FROM trip_files WHERE id = ?').get(info.lastInsertRowid) as { id: number; description: string | null; place_id: number | null; reservation_id: number | null };
|
||||
file_size: overrides.file_size === undefined ? 2 : overrides.file_size,
|
||||
mime_type: overrides.mime_type === undefined ? 'application/pdf' : overrides.mime_type,
|
||||
description: overrides.description ?? null,
|
||||
uploadedByRef: overrides.uploaded_by ?? null,
|
||||
place: overrides.place_id ?? null,
|
||||
reservation: overrides.reservation_id ?? null,
|
||||
starred: overrides.starred ?? 0,
|
||||
deleted_at: overrides.deleted_at ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
/** Put real bytes where the storage layer will look for `filename`. */
|
||||
@@ -114,14 +123,14 @@ function storeBytes(filename: string, body: Buffer | string) {
|
||||
fs.writeFileSync(nodePath.join(fixture.root, filename), body);
|
||||
}
|
||||
|
||||
function fileRow(id: number) {
|
||||
return testDb.prepare('SELECT description, place_id, reservation_id FROM trip_files WHERE id = ?').get(id) as
|
||||
{ description: string | null; place_id: number | null; reservation_id: number | null };
|
||||
async function fileRow(id: number) {
|
||||
const row = await findRow(orm, TripFiles, { id });
|
||||
if (!row) throw new Error(`no trip file ${id}`);
|
||||
return row;
|
||||
}
|
||||
|
||||
function linkRows(fileId: number) {
|
||||
return testDb.prepare('SELECT * FROM file_links WHERE file_id = ?').all(fileId) as
|
||||
{ id: number; reservation_id: number | null; assignment_id: number | null; place_id: number | null }[];
|
||||
return findRows(orm, FileLinks, { file: fileId });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -134,7 +143,7 @@ describe('Tool: list_trip_files', () => {
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const place = createPlace(testDb, trip.id);
|
||||
const reservation = createReservation(testDb, trip.id, { title: 'Hotel Ritz' });
|
||||
const file = insertFile(trip.id, {
|
||||
const file = await insertFile(trip.id, {
|
||||
original_name: 'boarding-pass.pdf', file_size: 4242, uploaded_by: user.id,
|
||||
place_id: place.id, reservation_id: reservation.id, starred: 1, description: 'AF1234',
|
||||
});
|
||||
@@ -160,8 +169,8 @@ describe('Tool: list_trip_files', () => {
|
||||
it('lists the trash only when asked for it', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
insertFile(trip.id, { original_name: 'live.pdf' });
|
||||
insertFile(trip.id, { original_name: 'trashed.pdf', deleted_at: '2027-01-01 10:00:00' });
|
||||
await insertFile(trip.id, { original_name: 'live.pdf' });
|
||||
await insertFile(trip.id, { original_name: 'trashed.pdf', deleted_at: '2027-01-01 10:00:00' });
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const live = parseToolResult(await h.client.callTool({ name: 'list_trip_files', arguments: { tripId: trip.id } })) as any;
|
||||
@@ -177,9 +186,9 @@ describe('Tool: list_trip_files', () => {
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const place = createPlace(testDb, trip.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
const file = insertFile(trip.id);
|
||||
testDb.prepare('INSERT INTO file_links (file_id, reservation_id) VALUES (?, ?)').run(file.id, reservation.id);
|
||||
testDb.prepare('INSERT INTO file_links (file_id, place_id) VALUES (?, ?)').run(file.id, place.id);
|
||||
const file = await insertFile(trip.id);
|
||||
await linkFile(orm, file.id, { reservation: reservation.id });
|
||||
await linkFile(orm, file.id, { place: place.id });
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const data = parseToolResult(await h.client.callTool({ name: 'list_trip_files', arguments: { tripId: trip.id } })) as any;
|
||||
@@ -192,7 +201,7 @@ describe('Tool: list_trip_files', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const { user: other } = createUser(testDb);
|
||||
const trip = createTrip(testDb, other.id);
|
||||
insertFile(trip.id);
|
||||
await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({ name: 'list_trip_files', arguments: { tripId: trip.id } });
|
||||
@@ -209,7 +218,7 @@ describe('Tool: read_trip_file', () => {
|
||||
it('returns a text document as text', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const file = insertFile(trip.id, { filename: 'notes.txt', original_name: 'packing-notes.txt', mime_type: 'text/plain', file_size: 11 });
|
||||
const file = await insertFile(trip.id, { filename: 'notes.txt', original_name: 'packing-notes.txt', mime_type: 'text/plain', file_size: 11 });
|
||||
storeBytes('notes.txt', 'Bring socks');
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
@@ -226,7 +235,7 @@ describe('Tool: read_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const bytes = Buffer.from([0x25, 0x50, 0x44, 0x46, 0x00, 0xff]);
|
||||
const file = insertFile(trip.id, { filename: 'stored.pdf', mime_type: 'application/pdf', file_size: bytes.length });
|
||||
const file = await insertFile(trip.id, { filename: 'stored.pdf', mime_type: 'application/pdf', file_size: bytes.length });
|
||||
storeBytes('stored.pdf', bytes);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
@@ -241,7 +250,7 @@ describe('Tool: read_trip_file', () => {
|
||||
it('falls back to a binary mimetype when the row has none', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const file = insertFile(trip.id, { filename: 'unknown.bin', mime_type: null, file_size: null });
|
||||
const file = await insertFile(trip.id, { filename: 'unknown.bin', mime_type: null, file_size: null });
|
||||
storeBytes('unknown.bin', 'hi');
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
@@ -257,7 +266,7 @@ describe('Tool: read_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const otherTrip = createTrip(testDb, user.id);
|
||||
const foreign = insertFile(otherTrip.id, { filename: 'foreign.txt', mime_type: 'text/plain' });
|
||||
const foreign = await insertFile(otherTrip.id, { filename: 'foreign.txt', mime_type: 'text/plain' });
|
||||
storeBytes('foreign.txt', 'secret');
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
@@ -270,7 +279,7 @@ describe('Tool: read_trip_file', () => {
|
||||
it('refuses a trashed file', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const file = insertFile(trip.id, { filename: 'trashed.txt', mime_type: 'text/plain', deleted_at: '2027-01-01 10:00:00' });
|
||||
const file = await insertFile(trip.id, { filename: 'trashed.txt', mime_type: 'text/plain', deleted_at: '2027-01-01 10:00:00' });
|
||||
storeBytes('trashed.txt', 'gone');
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
@@ -285,7 +294,7 @@ describe('Tool: read_trip_file', () => {
|
||||
const trip = createTrip(testDb, user.id);
|
||||
// Recorded size only: the refusal happens before the storage layer is asked,
|
||||
// which is the whole point of capping on the row.
|
||||
const file = insertFile(trip.id, { filename: 'huge.mp4', mime_type: 'video/mp4', file_size: FILE_CONTENT_MAX + 1 });
|
||||
const file = await insertFile(trip.id, { filename: 'huge.mp4', mime_type: 'video/mp4', file_size: FILE_CONTENT_MAX + 1 });
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({ name: 'read_trip_file', arguments: { tripId: trip.id, fileId: file.id } });
|
||||
@@ -297,7 +306,7 @@ describe('Tool: read_trip_file', () => {
|
||||
it('reports a row whose bytes are gone as unavailable, not as a crash', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const file = insertFile(trip.id, { filename: 'never-stored.pdf' });
|
||||
const file = await insertFile(trip.id, { filename: 'never-stored.pdf' });
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({ name: 'read_trip_file', arguments: { tripId: trip.id, fileId: file.id } });
|
||||
@@ -309,7 +318,7 @@ describe('Tool: read_trip_file', () => {
|
||||
it('lets an unexpected storage failure surface instead of calling the file missing', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const file = insertFile(trip.id, { filename: 'flaky.txt', mime_type: 'text/plain' });
|
||||
const file = await insertFile(trip.id, { filename: 'flaky.txt', mime_type: 'text/plain' });
|
||||
storeBytes('flaky.txt', 'hi');
|
||||
// A backend outage is not one of the three refusals: reporting it as "not
|
||||
// found" would send the caller off to fix a file that is perfectly fine.
|
||||
@@ -332,7 +341,7 @@ describe('Tool: read_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const { user: other } = createUser(testDb);
|
||||
const trip = createTrip(testDb, other.id);
|
||||
const file = insertFile(trip.id, { filename: 'members-only.txt', mime_type: 'text/plain' });
|
||||
const file = await insertFile(trip.id, { filename: 'members-only.txt', mime_type: 'text/plain' });
|
||||
storeBytes('members-only.txt', 'secret');
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
@@ -352,7 +361,7 @@ describe('Tool: update_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const place = createPlace(testDb, trip.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const data = parseToolResult(await h.client.callTool({
|
||||
@@ -360,7 +369,7 @@ describe('Tool: update_trip_file', () => {
|
||||
arguments: { tripId: trip.id, fileId: file.id, description: 'Museum ticket', place_id: place.id },
|
||||
})) as any;
|
||||
expect(data.file.description).toBe('Museum ticket');
|
||||
expect(fileRow(file.id)).toMatchObject({ description: 'Museum ticket', place_id: place.id });
|
||||
expect(await fileRow(file.id)).toMatchObject({ description: 'Museum ticket', place_id: place.id });
|
||||
});
|
||||
expect(broadcastMock).toHaveBeenCalledWith(trip.id, 'file:updated', expect.objectContaining({ _source: 'mcp' }));
|
||||
});
|
||||
@@ -369,14 +378,14 @@ describe('Tool: update_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
const file = insertFile(trip.id, { description: 'Keep me' });
|
||||
const file = await insertFile(trip.id, { description: 'Keep me' });
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
await h.client.callTool({
|
||||
name: 'update_trip_file',
|
||||
arguments: { tripId: trip.id, fileId: file.id, reservation_id: reservation.id },
|
||||
});
|
||||
expect(fileRow(file.id)).toMatchObject({ description: 'Keep me', reservation_id: reservation.id });
|
||||
expect(await fileRow(file.id)).toMatchObject({ description: 'Keep me', reservation_id: reservation.id });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -384,14 +393,14 @@ describe('Tool: update_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
const file = insertFile(trip.id, { reservation_id: reservation.id });
|
||||
const file = await insertFile(trip.id, { reservation_id: reservation.id });
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
await h.client.callTool({
|
||||
name: 'update_trip_file',
|
||||
arguments: { tripId: trip.id, fileId: file.id, reservation_id: null },
|
||||
});
|
||||
expect(fileRow(file.id).reservation_id).toBeNull();
|
||||
expect((await fileRow(file.id)).reservation_id).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -400,7 +409,7 @@ describe('Tool: update_trip_file', () => {
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const otherTrip = createTrip(testDb, user.id);
|
||||
const foreignPlace = createPlace(testDb, otherTrip.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -409,7 +418,7 @@ describe('Tool: update_trip_file', () => {
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(JSON.stringify(result.content)).toContain('does not belong to this trip (place_id)');
|
||||
expect(fileRow(file.id).place_id).toBeNull();
|
||||
expect((await fileRow(file.id)).place_id).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -417,7 +426,7 @@ describe('Tool: update_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const otherTrip = createTrip(testDb, user.id);
|
||||
const foreign = insertFile(otherTrip.id);
|
||||
const foreign = await insertFile(otherTrip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -434,7 +443,7 @@ describe('Tool: update_trip_file', () => {
|
||||
const { user: member } = createUser(testDb);
|
||||
const trip = createTrip(testDb, owner.id);
|
||||
addTripMember(testDb, trip.id, member.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
await setPermission('file_edit', 'trip_owner');
|
||||
|
||||
await withHarness(member.id, async (h) => {
|
||||
@@ -443,7 +452,7 @@ describe('Tool: update_trip_file', () => {
|
||||
arguments: { tripId: trip.id, fileId: file.id, description: 'nope' },
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(fileRow(file.id).description).toBeNull();
|
||||
expect((await fileRow(file.id)).description).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -451,7 +460,7 @@ describe('Tool: update_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const { user: other } = createUser(testDb);
|
||||
const trip = createTrip(testDb, other.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -467,7 +476,7 @@ describe('Tool: update_trip_file', () => {
|
||||
process.env.DEMO_MODE = 'true';
|
||||
const { user } = createUser(testDb, { email: 'demo@trek.app' });
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -475,7 +484,7 @@ describe('Tool: update_trip_file', () => {
|
||||
arguments: { tripId: trip.id, fileId: file.id, description: 'nope' },
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(fileRow(file.id).description).toBeNull();
|
||||
expect((await fileRow(file.id)).description).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -517,17 +526,17 @@ describe('Tool: upload_trip_file', () => {
|
||||
expect(JSON.stringify(result.content)).toContain('not allowed');
|
||||
}
|
||||
});
|
||||
expect(testDb.prepare('SELECT COUNT(*) AS n FROM trip_files').get()).toEqual({ n: 0 });
|
||||
expect(await countRows(orm, TripFiles)).toBe(0);
|
||||
});
|
||||
|
||||
it('follows the operator list, including the wildcard', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('allowed_file_types', 'pdf')").run();
|
||||
await setAppSetting(orm, 'allowed_file_types', 'pdf');
|
||||
await withHarness(user.id, async (h) => {
|
||||
const refused = await h.client.callTool({ name: 'upload_trip_file', arguments: { tripId: trip.id, filename: 'notes.txt', content: pdf } });
|
||||
expect(refused.isError).toBe(true);
|
||||
testDb.prepare("UPDATE app_settings SET value = '*' WHERE key = 'allowed_file_types'").run();
|
||||
await updateRows(orm, AppSettings, { key: 'allowed_file_types' }, { value: '*' });
|
||||
const data = parseToolResult(await h.client.callTool({ name: 'upload_trip_file', arguments: { tripId: trip.id, filename: 'route.gpx', content: pdf } })) as any;
|
||||
expect(data.file.mime_type).toBe('application/gpx+xml');
|
||||
});
|
||||
@@ -566,7 +575,7 @@ describe('Tool: upload_trip_file', () => {
|
||||
expect(result.isError).toBe(true);
|
||||
expect(JSON.stringify(result.content)).toContain('does not belong to this trip');
|
||||
});
|
||||
expect(testDb.prepare('SELECT COUNT(*) AS n FROM trip_files').get()).toEqual({ n: 0 });
|
||||
expect(await countRows(orm, TripFiles)).toBe(0);
|
||||
});
|
||||
|
||||
it('refuses a member once file_upload is owner-only', async () => {
|
||||
@@ -596,7 +605,7 @@ describe('Tool: upload_trip_file', () => {
|
||||
const result = await h.client.callTool({ name: 'upload_trip_file', arguments: { tripId: own.id, filename: 'a.pdf', content: pdf } });
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
expect(testDb.prepare('SELECT COUNT(*) AS n FROM trip_files').get()).toEqual({ n: 0 });
|
||||
expect(await countRows(orm, TripFiles)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -612,7 +621,7 @@ describe('Tool: link_trip_file', () => {
|
||||
const place = createPlace(testDb, trip.id);
|
||||
const day = createDay(testDb, trip.id);
|
||||
const assignment = createDayAssignment(testDb, day.id, place.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const first = parseToolResult(await h.client.callTool({
|
||||
@@ -628,7 +637,7 @@ describe('Tool: link_trip_file', () => {
|
||||
});
|
||||
});
|
||||
|
||||
const links = linkRows(file.id);
|
||||
const links = await linkRows(file.id);
|
||||
expect(links).toHaveLength(2);
|
||||
expect(links[0].reservation_id).toBe(reservation.id);
|
||||
expect(links[1]).toMatchObject({ place_id: place.id, assignment_id: assignment.id });
|
||||
@@ -637,14 +646,14 @@ describe('Tool: link_trip_file', () => {
|
||||
it('refuses a call with no target instead of storing an empty link', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({ name: 'link_trip_file', arguments: { tripId: trip.id, fileId: file.id } });
|
||||
expect(result.isError).toBe(true);
|
||||
expect(JSON.stringify(result.content)).toContain('at least one of reservation_id');
|
||||
});
|
||||
expect(linkRows(file.id)).toHaveLength(0);
|
||||
expect(await linkRows(file.id)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('refuses a booking from another trip', async () => {
|
||||
@@ -652,7 +661,7 @@ describe('Tool: link_trip_file', () => {
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const otherTrip = createTrip(testDb, user.id);
|
||||
const foreignReservation = createReservation(testDb, otherTrip.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -662,7 +671,7 @@ describe('Tool: link_trip_file', () => {
|
||||
expect(result.isError).toBe(true);
|
||||
expect(JSON.stringify(result.content)).toContain('does not belong to this trip (reservation_id)');
|
||||
});
|
||||
expect(linkRows(file.id)).toHaveLength(0);
|
||||
expect(await linkRows(file.id)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('refuses a day assignment from another trip', async () => {
|
||||
@@ -672,7 +681,7 @@ describe('Tool: link_trip_file', () => {
|
||||
const foreignDay = createDay(testDb, otherTrip.id);
|
||||
const foreignPlace = createPlace(testDb, otherTrip.id);
|
||||
const foreignAssignment = createDayAssignment(testDb, foreignDay.id, foreignPlace.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -688,7 +697,7 @@ describe('Tool: link_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const otherTrip = createTrip(testDb, user.id);
|
||||
const foreign = insertFile(otherTrip.id);
|
||||
const foreign = await insertFile(otherTrip.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
@@ -707,7 +716,7 @@ describe('Tool: link_trip_file', () => {
|
||||
const trip = createTrip(testDb, owner.id);
|
||||
addTripMember(testDb, trip.id, member.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
await setPermission('file_edit', 'trip_owner');
|
||||
|
||||
await withHarness(member.id, async (h) => {
|
||||
@@ -717,7 +726,7 @@ describe('Tool: link_trip_file', () => {
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
expect(linkRows(file.id)).toHaveLength(0);
|
||||
expect(await linkRows(file.id)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('denies a non-member', async () => {
|
||||
@@ -725,7 +734,7 @@ describe('Tool: link_trip_file', () => {
|
||||
const { user: other } = createUser(testDb);
|
||||
const trip = createTrip(testDb, other.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -742,7 +751,7 @@ describe('Tool: link_trip_file', () => {
|
||||
const { user } = createUser(testDb, { email: 'demo@trek.app' });
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -751,7 +760,7 @@ describe('Tool: link_trip_file', () => {
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
expect(linkRows(file.id)).toHaveLength(0);
|
||||
expect(await linkRows(file.id)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -765,10 +774,10 @@ describe('Tool: unlink_trip_file', () => {
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
const place = createPlace(testDb, trip.id);
|
||||
const file = insertFile(trip.id);
|
||||
testDb.prepare('INSERT INTO file_links (file_id, reservation_id) VALUES (?, ?)').run(file.id, reservation.id);
|
||||
testDb.prepare('INSERT INTO file_links (file_id, place_id) VALUES (?, ?)').run(file.id, place.id);
|
||||
const doomed = linkRows(file.id)[0].id;
|
||||
const file = await insertFile(trip.id);
|
||||
await linkFile(orm, file.id, { reservation: reservation.id });
|
||||
await linkFile(orm, file.id, { place: place.id });
|
||||
const doomed = (await linkRows(file.id))[0].id;
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const data = parseToolResult(await h.client.callTool({
|
||||
@@ -777,7 +786,7 @@ describe('Tool: unlink_trip_file', () => {
|
||||
})) as any;
|
||||
expect(data.success).toBe(true);
|
||||
});
|
||||
const left = linkRows(file.id);
|
||||
const left = await linkRows(file.id);
|
||||
expect(left).toHaveLength(1);
|
||||
expect(left[0].place_id).toBe(place.id);
|
||||
});
|
||||
@@ -786,10 +795,10 @@ describe('Tool: unlink_trip_file', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const otherTrip = createTrip(testDb, user.id);
|
||||
const foreignFile = insertFile(otherTrip.id);
|
||||
const foreignFile = await insertFile(otherTrip.id);
|
||||
const foreignReservation = createReservation(testDb, otherTrip.id);
|
||||
testDb.prepare('INSERT INTO file_links (file_id, reservation_id) VALUES (?, ?)').run(foreignFile.id, foreignReservation.id);
|
||||
const linkId = linkRows(foreignFile.id)[0].id;
|
||||
await linkFile(orm, foreignFile.id, { reservation: foreignReservation.id });
|
||||
const linkId = (await linkRows(foreignFile.id))[0].id;
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -799,7 +808,7 @@ describe('Tool: unlink_trip_file', () => {
|
||||
expect(result.isError).toBe(true);
|
||||
expect(JSON.stringify(result.content)).toContain('File not found.');
|
||||
});
|
||||
expect(linkRows(foreignFile.id)).toHaveLength(1);
|
||||
expect(await linkRows(foreignFile.id)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('refuses a member once file_edit is owner-only', async () => {
|
||||
@@ -808,9 +817,9 @@ describe('Tool: unlink_trip_file', () => {
|
||||
const trip = createTrip(testDb, owner.id);
|
||||
addTripMember(testDb, trip.id, member.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
const file = insertFile(trip.id);
|
||||
testDb.prepare('INSERT INTO file_links (file_id, reservation_id) VALUES (?, ?)').run(file.id, reservation.id);
|
||||
const linkId = linkRows(file.id)[0].id;
|
||||
const file = await insertFile(trip.id);
|
||||
await linkFile(orm, file.id, { reservation: reservation.id });
|
||||
const linkId = (await linkRows(file.id))[0].id;
|
||||
await setPermission('file_edit', 'trip_owner');
|
||||
|
||||
await withHarness(member.id, async (h) => {
|
||||
@@ -820,14 +829,14 @@ describe('Tool: unlink_trip_file', () => {
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
expect(linkRows(file.id)).toHaveLength(1);
|
||||
expect(await linkRows(file.id)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('denies a non-member', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const { user: other } = createUser(testDb);
|
||||
const trip = createTrip(testDb, other.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -844,9 +853,9 @@ describe('Tool: unlink_trip_file', () => {
|
||||
const { user } = createUser(testDb, { email: 'demo@nomad.app' });
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const reservation = createReservation(testDb, trip.id);
|
||||
const file = insertFile(trip.id);
|
||||
testDb.prepare('INSERT INTO file_links (file_id, reservation_id) VALUES (?, ?)').run(file.id, reservation.id);
|
||||
const linkId = linkRows(file.id)[0].id;
|
||||
const file = await insertFile(trip.id);
|
||||
await linkFile(orm, file.id, { reservation: reservation.id });
|
||||
const linkId = (await linkRows(file.id))[0].id;
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -855,7 +864,7 @@ describe('Tool: unlink_trip_file', () => {
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
});
|
||||
expect(linkRows(file.id)).toHaveLength(1);
|
||||
expect(await linkRows(file.id)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -868,8 +877,8 @@ describe('Tool: list_trip_file_links', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const reservation = createReservation(testDb, trip.id, { title: 'Nightjet 421' });
|
||||
const file = insertFile(trip.id);
|
||||
testDb.prepare('INSERT INTO file_links (file_id, reservation_id) VALUES (?, ?)').run(file.id, reservation.id);
|
||||
const file = await insertFile(trip.id);
|
||||
await linkFile(orm, file.id, { reservation: reservation.id });
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const data = parseToolResult(await h.client.callTool({
|
||||
@@ -885,7 +894,7 @@ describe('Tool: list_trip_file_links', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const otherTrip = createTrip(testDb, user.id);
|
||||
const foreign = insertFile(otherTrip.id);
|
||||
const foreign = await insertFile(otherTrip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
@@ -900,7 +909,7 @@ describe('Tool: list_trip_file_links', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const { user: other } = createUser(testDb);
|
||||
const trip = createTrip(testDb, other.id);
|
||||
const file = insertFile(trip.id);
|
||||
const file = await insertFile(trip.id);
|
||||
|
||||
await withHarness(user.id, async (h) => {
|
||||
const result = await h.client.callTool({
|
||||
|
||||
Reference in New Issue
Block a user