mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
91 lines
3.9 KiB
TypeScript
91 lines
3.9 KiB
TypeScript
/**
|
|
* Calendar-feed visibility e2e — proves the staged-booking gate holds on the
|
|
* one path that needs no session at all: the public /api/feed/trip/:token.ics.
|
|
*
|
|
* tests/e2e/feeds.e2e.test.ts cannot cover this. It mocks buildTripCalendar and
|
|
* never creates a reservations table, so it owns the calendar parts and the SQL
|
|
* filter is invisible to it. This file runs the REAL CalendarService against a
|
|
* real migrated SQLite instead, and asserts on the bytes a calendar client
|
|
* would receive.
|
|
*/
|
|
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
|
import request from 'supertest';
|
|
import type { Server } from 'http';
|
|
import { Test } from '@nestjs/testing';
|
|
|
|
const { db } = vi.hoisted(() => {
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
const Database = require('better-sqlite3');
|
|
const tmp = new Database(':memory:');
|
|
tmp.exec('PRAGMA journal_mode = WAL');
|
|
return { db: tmp };
|
|
});
|
|
|
|
vi.mock('../../src/db/database', () => ({
|
|
db,
|
|
canAccessTrip: (tripId: number | string, userId: number) =>
|
|
db.prepare('SELECT id, user_id FROM trips WHERE id = ? AND user_id = ?').get(tripId, userId),
|
|
isOwner: () => true,
|
|
getPlaceWithTags: () => null,
|
|
closeDb: () => {},
|
|
reinitialize: () => {},
|
|
}));
|
|
|
|
import { createTables } from '../../src/db/schema';
|
|
import { runMigrations } from '../../src/db/migrations';
|
|
import { DatabaseModule } from '../../src/nest/database/database.module';
|
|
import { FeedsModule } from '../../src/nest/feeds/feeds.module';
|
|
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
|
|
import { TestUnitOfWorkModule } from '../helpers/test-uow';
|
|
import { createTestMikroOrmModule } from '../helpers/test-orm';
|
|
|
|
describe('Calendar feed visibility e2e (real CalendarService over temp SQLite)', () => {
|
|
let server: Server;
|
|
let app: Awaited<ReturnType<typeof build>>;
|
|
let feedToken: string;
|
|
|
|
async function build() {
|
|
const moduleRef = await Test.createTestingModule({ imports: [await TestUnitOfWorkModule.forRoot(db), await createTestMikroOrmModule(db), DatabaseModule, FeedsModule] }).compile();
|
|
const nest = moduleRef.createNestApplication();
|
|
nest.useGlobalFilters(new TrekExceptionFilter());
|
|
await nest.init();
|
|
return nest;
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
createTables(db);
|
|
runMigrations(db);
|
|
db.prepare(
|
|
"INSERT INTO users (id, username, email, password_hash, role) VALUES (1, 'e2e-user', 'e2e@example.test', 'x', 'user')",
|
|
).run();
|
|
feedToken = 'feed-token-visibility';
|
|
db.prepare(
|
|
"INSERT INTO trips (id, user_id, title, start_date, end_date, feed_token) VALUES (1, 1, 'Kyoto', '2026-09-01', '2026-09-05', ?)",
|
|
).run(feedToken);
|
|
db.prepare("INSERT INTO days (id, trip_id, day_number, date) VALUES (1, 1, 1, '2026-09-01')").run();
|
|
db.prepare(`INSERT INTO reservations (trip_id, day_id, title, type, status, reservation_time, confirmation_number, ingest_state)
|
|
VALUES (1, 1, 'Parked Flight', 'flight', 'confirmed', '2026-09-01T08:00', 'SECRET1', 'staged')`).run();
|
|
db.prepare(`INSERT INTO reservations (trip_id, day_id, title, type, status, reservation_time, confirmation_number)
|
|
VALUES (1, 1, 'Booked Flight', 'flight', 'confirmed', '2026-09-01T12:00', 'OPEN1')`).run();
|
|
|
|
app = await build();
|
|
server = app.getHttpServer();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
it('the public feed serves the live booking and neither the staged one nor its confirmation number', async () => {
|
|
const res = await request(server).get(`/api/feed/trip/${feedToken}.ics`);
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers['content-type']).toContain('text/calendar');
|
|
expect(res.text).toContain('SUMMARY:Booked Flight');
|
|
expect(res.text).not.toContain('Parked Flight');
|
|
// The number rides in the DESCRIPTION, so search the whole document.
|
|
expect(res.text).not.toContain('SECRET1');
|
|
expect(res.text).toContain('OPEN1');
|
|
});
|
|
});
|