mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 22:16:26 +00:00
test(tours): bring the server tests in line with the tours board
Expectations follow createAssignment's options, the stage_ends column, the editor's trip access and the broadcasts of the take-over. Refusals in the run and ski e2e tests go one by one so supertest does not reset a connection under another request. A parity test keeps the source hint enums in step with the contract.
This commit is contained in:
@@ -211,6 +211,8 @@ describe('Collection tours e2e (real guards + temp SQLite)', () => {
|
||||
.put(`/api/trips/5/tours/${source}`)
|
||||
.set(as(OWNER))
|
||||
.send(tourBody({ name: 'Changed later' }));
|
||||
// Making the source and changing it told trip 5; from here on only the trip it is taken into hears of it.
|
||||
realtime.broadcastMock.mockClear();
|
||||
|
||||
const full = await request(server)
|
||||
.post('/api/trips/6/tours/from-collection')
|
||||
|
||||
@@ -30,6 +30,7 @@ import { setAddonEnabled } from '../helpers/factories/settings';
|
||||
import { addTripMember } from '../helpers/factories/trips';
|
||||
import { makeUser } from '../helpers/factories/users';
|
||||
import { FakeRealtimeService } from '../helpers/fake-realtime';
|
||||
import { oneByOne } from '../helpers/one-by-one';
|
||||
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
|
||||
import { TestUnitOfWorkModule } from '../helpers/test-uow';
|
||||
import { sessionCookie } from './harness';
|
||||
@@ -205,7 +206,7 @@ describe('Runs e2e (real guards + temp SQLite)', () => {
|
||||
});
|
||||
|
||||
it('TOURS-RUN-E2E-004: refuses a run without a profile, a pace out of bounds or not positive, and run fields on a hike', async () => {
|
||||
const refused = await Promise.all(
|
||||
const refused = await oneByOne(
|
||||
[
|
||||
runBody({ run_profile: null }),
|
||||
runBody({ run_profile: 'road' }),
|
||||
@@ -215,7 +216,8 @@ describe('Runs e2e (real guards + temp SQLite)', () => {
|
||||
runBody({ run_pace_seconds_per_km: 330.5 }),
|
||||
runBody({ max_hiking_difficulty: 2 }),
|
||||
runBody({ tour_type: 'hike', run_profile: undefined }),
|
||||
].map((body) => send('post', '', body)),
|
||||
],
|
||||
(body) => send('post', '', body),
|
||||
);
|
||||
expect(refused.map((res) => res.status)).toEqual([400, 400, 400, 400, 400, 400, 400, 400]);
|
||||
expect(await countRows(orm, Tours)).toBe(0);
|
||||
|
||||
@@ -30,6 +30,7 @@ import { setAddonEnabled } from '../helpers/factories/settings';
|
||||
import { addTripMember } from '../helpers/factories/trips';
|
||||
import { makeUser } from '../helpers/factories/users';
|
||||
import { FakeRealtimeService } from '../helpers/fake-realtime';
|
||||
import { oneByOne } from '../helpers/one-by-one';
|
||||
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
|
||||
import { TestUnitOfWorkModule } from '../helpers/test-uow';
|
||||
import { sessionCookie } from './harness';
|
||||
@@ -208,14 +209,15 @@ describe('Ski tracks e2e (real guards + temp SQLite)', () => {
|
||||
);
|
||||
|
||||
it('TOURS-SKI-E2E-003: refuses a ski import without its discipline or with a technique it has not, and ski fields on a hike', async () => {
|
||||
const refused = await Promise.all(
|
||||
const refused = await oneByOne(
|
||||
[
|
||||
{ tour_type: 'ski' },
|
||||
{ tour_type: 'ski', ski_discipline: 'cross_country' },
|
||||
{ tour_type: 'ski', ski_discipline: 'alpine', ski_technique: 'classic' },
|
||||
{ tour_type: 'ski', ski_discipline: 'freeride' },
|
||||
{ tour_type: 'hike', ski_discipline: 'alpine' },
|
||||
].map((fields) => importGpx(fields)),
|
||||
],
|
||||
(fields) => importGpx(fields),
|
||||
);
|
||||
expect(refused.map((res) => res.status)).toEqual([400, 400, 400, 400, 400]);
|
||||
expect(await countRows(orm, Places)).toBe(0);
|
||||
@@ -272,13 +274,14 @@ describe('Ski tracks e2e (real guards + temp SQLite)', () => {
|
||||
|
||||
it('TOURS-SKI-E2E-007: refuses a classification that is no ski classification, or of a tour that is no ski track', async () => {
|
||||
const placeId = await importSki({ ski_discipline: 'alpine' });
|
||||
const invalid = await Promise.all(
|
||||
const invalid = await oneByOne(
|
||||
[
|
||||
{},
|
||||
{ ski_discipline: 'cross_country' },
|
||||
{ ski_discipline: 'touring', ski_technique: 'classic' },
|
||||
{ ski_discipline: 'monoski' },
|
||||
].map((body) => classify(placeId, body)),
|
||||
],
|
||||
(body) => classify(placeId, body),
|
||||
);
|
||||
expect(invalid.map((res) => res.status)).toEqual([400, 400, 400, 400]);
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Sends requests one after another and answers their results in order.
|
||||
*
|
||||
* Supertest given an app's HTTP server that is not listening opens it on a
|
||||
* port of its own for every request and closes it again once that request is
|
||||
* answered. Several requests sent together against the same server race: a
|
||||
* refusal answered at once closes it under another one still in flight, which
|
||||
* then fails with ECONNRESET instead of its status. Sent one by one, each
|
||||
* request has the server to itself.
|
||||
*/
|
||||
export async function oneByOne<T, R>(items: readonly T[], send: (item: T) => PromiseLike<R>): Promise<R[]> {
|
||||
return await items.reduce<Promise<R[]>>(
|
||||
async (sent, item) => [...(await sent), await send(item)],
|
||||
Promise.resolve([]),
|
||||
);
|
||||
}
|
||||
@@ -386,6 +386,7 @@ describe('ToursRepository writes', () => {
|
||||
ski_technique: null,
|
||||
track_started_at: null,
|
||||
track_ended_at: null,
|
||||
stage_ends: 0,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { TourSourceHintsReason, TourSourceHintsState } from '../../../src/db/entities/TourSourceHints.entity';
|
||||
import { tourSourceHintReasonSchema, tourSourceHintStateSchema } from '@trek/shared';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
/**
|
||||
* WAN-02: a hint's state and reason are CHECK lists (migration 046120) the
|
||||
* entity carries as enums, and the contract holds its own. A value one side
|
||||
* knows and the other refuses would be a hint the watch can find and the
|
||||
* database cannot store, or one stored that the answer cannot describe.
|
||||
*/
|
||||
describe('wanderer source hint columns match the contract', () => {
|
||||
it('TOURHINTCOL-001: knows the same states as the hint contract', () => {
|
||||
expect(Object.values(TourSourceHintsState).sort()).toEqual([...tourSourceHintStateSchema.options].sort());
|
||||
});
|
||||
|
||||
it('TOURHINTCOL-002: knows the same reasons as the hint contract', () => {
|
||||
expect(Object.values(TourSourceHintsReason).sort()).toEqual([...tourSourceHintReasonSchema.options].sort());
|
||||
});
|
||||
});
|
||||
@@ -159,6 +159,8 @@ describe('CollectionToursService', () => {
|
||||
|
||||
it('LIB3-SVC-002: keeps it under another name, an editor may keep one too', async () => {
|
||||
const f = await fixture();
|
||||
// An editor of the list keeps only a tour of a trip they can open themselves.
|
||||
await addTripMember(orm, f.alps.id, f.editor.id);
|
||||
const { tour } = await service.save(f.editor.id, f.list.id, {
|
||||
source_trip_id: f.alps.id,
|
||||
source_place_id: f.ridge.id,
|
||||
|
||||
@@ -231,11 +231,11 @@ describe('TourChangeMcp (MCP-T02)', () => {
|
||||
});
|
||||
expect(read(await prepare(f.trip.id, f.bakery.id, changes, f.user.id))).toEqual({ error: 'Tour not found.' });
|
||||
const failure = new Error('database gone');
|
||||
const prepare = vi.spyOn(t.get(TourChangePreviewService), 'prepare').mockRejectedValueOnce(failure);
|
||||
const servicePrepare = vi.spyOn(t.get(TourChangePreviewService), 'prepare').mockRejectedValueOnce(failure);
|
||||
try {
|
||||
await expect(prepare(f.trip.id, f.walk.id, changes, f.user.id)).rejects.toBe(failure);
|
||||
} finally {
|
||||
prepare.mockRestore();
|
||||
servicePrepare.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ describe('Tours deletion and assignment authorization (mock-only)', () => {
|
||||
if (handler === 'create') {
|
||||
expect(assignments.dayExists).toHaveBeenCalledWith('11', '7');
|
||||
expect(assignments.placeExists).toHaveBeenCalledWith(42, '7');
|
||||
expect(assignments.createAssignment).toHaveBeenCalledWith('11', 42, 'Tour');
|
||||
expect(assignments.createAssignment).toHaveBeenCalledWith('11', 42, 'Tour', {});
|
||||
expect(assignments.deleteAssignment).not.toHaveBeenCalled();
|
||||
expect(assignments.broadcast).toHaveBeenCalledWith('7', 'assignment:created', { assignment }, 'socket');
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user