mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 05:56:53 +00:00
06e6e25b25
While a hike is planned, the planner's map switch offers "Hiking" beside the map, topo and satellite: OpenHikingMap, with marked trails over the terrain. It is a choice of the planner's view alone, like topo: the global map setting, the tour and its days stay as they are. With no tour open, over an import viewed as it came in and for a paddle tour it is not offered, and a choice made earlier falls back to the standard map there. openmaps.fr's tile usage policy is followed: tiles load only as the map shows them, with the browser's Referer, from the server's own caching; no service worker rule keeps them and the 3D flyover's warm-up passes their source by. Its credit and donation link are on the map; the 3D map, whose attribution is folded away under the panel, shows them as a line of its own above its controls. The service is best effort. A run of failed tiles while it is shown marks the switch, with a tooltip and a status for screen readers that say it is not answering and that the tour is untouched; one tile arriving clears it. The tile health the seamark overlay had is now useTileHealth, shared by both. The source is free for non-commercial installs up to 500,000 tiles a month. tours_hiking_map_enabled is an admin switch under Tours (admin tours-hiking-map, audited), on by default, for an install that is commercial or larger to switch off until it has an agreement with the operator. /api/addons carries it as toursHikingMap, true only while Tours is on. Leaflet, GL and the 3D map read one table of the planner's raster bases (tourRasterBases), so topo and the hiking map are drawn the same way. tile.openmaps.fr is on the CSP's connect-src for the GL maps.
184 lines
7.0 KiB
TypeScript
184 lines
7.0 KiB
TypeScript
/**
|
|
* GET /api/addons e2e — exercises the AddonsController through the real
|
|
* JwtAuthGuard against a real migrated-and-seeded temp SQLite db
|
|
* (createSnapshotTestDb(), task-6-fix-brief.md item 5 — this used to hand-roll
|
|
* four CREATE TABLEs, a second hand-maintained schema copy whose NOT
|
|
* NULL/defaults/FK/UNIQUE drifted from the real migrations). getPhotoProviderConfig
|
|
* is mocked; the addons/photo_providers/photo_provider_fields/app_settings reads
|
|
* run against the temp db (the collab/bag-tracking flags are real AddonsService
|
|
* reads since the admin-1 extraction). Asserts the byte-identical body the legacy
|
|
* inline handler produced.
|
|
*/
|
|
import { db } from '../../src/db/database';
|
|
import { Addons } from '../../src/db/entities/Addons.entity';
|
|
import { PhotoProviderFields } from '../../src/db/entities/PhotoProviderFields.entity';
|
|
import { PhotoProviders } from '../../src/db/entities/PhotoProviders.entity';
|
|
import { AddonsModule } from '../../src/nest/addons/addons.module';
|
|
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
|
|
import { findRow, insertRow } from '../helpers/factories/rows';
|
|
import { setAddonEnabled, setAppSetting } from '../helpers/factories/settings';
|
|
import { makeUser } from '../helpers/factories/users';
|
|
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
|
|
import { TestUnitOfWorkModule } from '../helpers/test-uow';
|
|
import { sessionCookie } from './harness';
|
|
import { Test } from '@nestjs/testing';
|
|
|
|
import cookieParser from 'cookie-parser';
|
|
import type { Server } from 'http';
|
|
import request from 'supertest';
|
|
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
|
|
|
vi.mock('../../src/db/database', async () => {
|
|
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
|
|
return buildDbMock(createSnapshotTestDb());
|
|
});
|
|
|
|
// The snapshot ships the real seeded catalogue (production default addons +
|
|
// photo providers) — this suite wants a known, empty set of each so every
|
|
// case controls its own fixture rows exactly like the legacy hand-rolled
|
|
// tables did. Child table first for the FK on photo_provider_fields.
|
|
db.exec('DELETE FROM photo_provider_fields');
|
|
db.exec('DELETE FROM photo_providers');
|
|
db.exec('DELETE FROM addons');
|
|
db.exec('DELETE FROM app_settings');
|
|
|
|
const { getPhotoProviderConfig } = vi.hoisted(() => ({
|
|
getPhotoProviderConfig: vi.fn(() => ({ url: 'https://immich.example' })),
|
|
}));
|
|
vi.mock('../../src/nest/common/photo-provider-config', () => ({ getPhotoProviderConfig }));
|
|
|
|
let orm: TestOrm;
|
|
|
|
describe('GET /api/addons e2e (real auth guard + temp SQLite)', () => {
|
|
let server: Server;
|
|
let app: Awaited<ReturnType<typeof build>>;
|
|
|
|
async function build() {
|
|
const moduleRef = await Test.createTestingModule({
|
|
imports: [await TestUnitOfWorkModule.forRoot(db), await createTestMikroOrmModule(db), AddonsModule],
|
|
}).compile();
|
|
const nest = moduleRef.createNestApplication();
|
|
nest.use(cookieParser());
|
|
nest.useGlobalFilters(new TrekExceptionFilter());
|
|
await nest.init();
|
|
return nest;
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
// harness.ts's seedUser() omits password_hash, which the real migrated
|
|
// schema requires NOT NULL (share.e2e.test.ts hit the same thing) — a
|
|
// raw insert here instead, matching the SeededUser shape id/role/
|
|
// password_version=0 that sessionCookie(1) needs.
|
|
orm = await createTestOrm(db);
|
|
await makeUser(orm, { id: 1, username: 'e2e-user', email: 'e2e@example.test' });
|
|
// bag tracking is opt-in (=== 'true'); collab flags default ON with no rows
|
|
await setAppSetting(orm, 'bag_tracking_enabled', 'true');
|
|
await insertRow(orm, Addons, {
|
|
id: 'packing',
|
|
name: 'Packing',
|
|
type: 'trip',
|
|
icon: 'Backpack',
|
|
enabled: true,
|
|
sort_order: 1,
|
|
});
|
|
await insertRow(orm, Addons, {
|
|
id: 'disabled',
|
|
name: 'Disabled',
|
|
type: 'trip',
|
|
icon: 'X',
|
|
enabled: false,
|
|
sort_order: 2,
|
|
});
|
|
// The providers ride the journey addon — without this row they are dropped from the listing.
|
|
await insertRow(orm, Addons, {
|
|
id: 'journey',
|
|
name: 'Journey',
|
|
type: 'global',
|
|
icon: 'Compass',
|
|
enabled: true,
|
|
sort_order: 3,
|
|
});
|
|
await insertRow(orm, PhotoProviders, { id: 'immich', name: 'Immich', icon: 'Image', enabled: 1, sort_order: 1 });
|
|
await insertRow(orm, PhotoProviderFields, {
|
|
provider: 'immich',
|
|
field_key: 'base_url',
|
|
label: 'Base URL',
|
|
input_type: 'text',
|
|
placeholder: 'https://...',
|
|
hint: null,
|
|
required: 1,
|
|
secret: 0,
|
|
settings_key: 'immich_url',
|
|
payload_key: null,
|
|
sort_order: 1,
|
|
});
|
|
app = await build();
|
|
server = app.getHttpServer();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
await orm.close();
|
|
});
|
|
|
|
it('401 without a cookie', async () => {
|
|
expect((await request(server).get('/api/addons')).status).toBe(401);
|
|
});
|
|
|
|
// Session 1 is a default-role ('user') account — i.e. a non-admin. Asserting the
|
|
// global bagTracking flag here is present is the #1124 regression guard: reading the
|
|
// toggle must not require admin.
|
|
it('200 returns enabled addons + photo providers (disabled addon excluded)', async () => {
|
|
const res = await request(server).get('/api/addons').set('Cookie', sessionCookie(1));
|
|
expect(res.status).toBe(200);
|
|
expect(res.body).toEqual({
|
|
collabFeatures: { chat: true, notes: true, links: true, polls: true, whatsnext: true },
|
|
bagTracking: true,
|
|
// Tours is off in this database, so wanderer is not offered either.
|
|
toursWanderer: false,
|
|
toursHikingMap: false,
|
|
addons: [
|
|
{ id: 'packing', name: 'Packing', type: 'trip', icon: 'Backpack', enabled: true },
|
|
{ id: 'journey', name: 'Journey', type: 'global', icon: 'Compass', enabled: true },
|
|
{
|
|
id: 'immich',
|
|
name: 'Immich',
|
|
type: 'photo_provider',
|
|
icon: 'Image',
|
|
enabled: true,
|
|
config: { url: 'https://immich.example' },
|
|
fields: [
|
|
{
|
|
key: 'base_url',
|
|
label: 'Base URL',
|
|
input_type: 'text',
|
|
placeholder: 'https://...',
|
|
hint: null,
|
|
required: true,
|
|
secret: false,
|
|
settings_key: 'immich_url',
|
|
payload_key: null,
|
|
sort_order: 1,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it('200 drops the photo providers while the journey addon is off', async () => {
|
|
await setAddonEnabled(orm, 'journey', false);
|
|
try {
|
|
const res = await request(server).get('/api/addons').set('Cookie', sessionCookie(1));
|
|
expect(res.status).toBe(200);
|
|
// journey leaves the enabled list AND takes immich with it, its row untouched
|
|
expect(res.body.addons).toEqual([
|
|
{ id: 'packing', name: 'Packing', type: 'trip', icon: 'Backpack', enabled: true },
|
|
]);
|
|
expect((await findRow(orm, PhotoProviders, { id: 'immich' }))!.enabled).toBe(1);
|
|
} finally {
|
|
await setAddonEnabled(orm, 'journey', true);
|
|
}
|
|
});
|
|
});
|