mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
Give the travel-stats cases a file of their own
atlas.service.test.ts carries the admin1 GeoJSON bundle warm-up from #48, and its ATLAS-UNIT-021..024 cases sit close to the 15s limit whenever the machine is busy. Eighteen more cases in the same file is not what they need. Same DB-per-file pattern, no shared state, case IDs unchanged.
This commit is contained in:
@@ -38,7 +38,7 @@ vi.mock('../../../src/db/database', () => dbMock);
|
||||
import { createTables } from '../../../src/db/schema';
|
||||
import { runMigrations } from '../../../src/db/migrations';
|
||||
import { resetTestDb } from '../../helpers/test-db';
|
||||
import { createUser, createTrip, createReservation, createPlace } from '../../helpers/factories';
|
||||
import { createUser, createTrip, createReservation } from '../../helpers/factories';
|
||||
import { getCached, setCache, getCountryFromCoords, getCountryFromAddress, isPointInCountryBox, reverseGeocodeCountry, getRegionGeo, getCountryGeo } from '../../../src/nest/atlas/atlas-geo';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
import { AtlasService } from '../../../src/nest/atlas/atlas.service';
|
||||
@@ -1408,139 +1408,3 @@ describe('getVisitedRegions — status (#1048)', () => {
|
||||
expect(after.regions['FR'][0].status).toBe('visited');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// getTravelStats — moved here from auth.service.test.ts together with the
|
||||
// method itself. Case IDs preserved so the history stays greppable.
|
||||
// ---------------------------------------------------------------------------
|
||||
// ── getTravelStats — dashboard passport card ────────────────────────────────
|
||||
|
||||
describe('getTravelStats', () => {
|
||||
function endpoint(reservationId: number, role: 'from' | 'to' | 'stop', sequence: number, lat: number, lng: number) {
|
||||
testDb.prepare(
|
||||
'INSERT INTO reservation_endpoints (reservation_id, role, sequence, name, lat, lng) VALUES (?, ?, ?, ?, ?, ?)'
|
||||
).run(reservationId, role, sequence, `Endpoint ${sequence}`, lat, lng);
|
||||
}
|
||||
|
||||
// Every trip below is dated in the past: since #1048 the passport card only counts
|
||||
// countries from trips that have already started, so a dateless fixture would make
|
||||
// these role-filter/tombstone assertions vacuous.
|
||||
const PAST_START = '2023-05-01';
|
||||
const PAST_END = '2023-05-10';
|
||||
|
||||
it('AUTH-DB-047: #1486 counts the from/to countries of a flight', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { title: 'Tokyo Trip', start_date: PAST_START, end_date: PAST_END });
|
||||
const res = createReservation(testDb, trip.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844); // Brussels
|
||||
endpoint(res.id, 'to', 1, 35.6762, 139.6503); // Tokyo
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
expect(stats.countries).toContain('BE');
|
||||
expect(stats.countries).toContain('JP');
|
||||
});
|
||||
|
||||
it('AUTH-DB-048: #1486 a connecting-flight layover does NOT count as visited', () => {
|
||||
// The Atlas query grew a role filter for #1486 but this copy of it did not, so the
|
||||
// dashboard passport card still counted a plane change as a visited country.
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { title: 'Connection Trip', start_date: PAST_START, end_date: PAST_END });
|
||||
const res = createReservation(testDb, trip.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844); // Brussels
|
||||
endpoint(res.id, 'stop', 1, 35.6762, 139.6503); // Tokyo — never leaves the airport
|
||||
endpoint(res.id, 'to', 2, -33.8688, 151.2093); // Sydney
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
expect(stats.countries).toContain('BE');
|
||||
expect(stats.countries).toContain('AU');
|
||||
expect(stats.countries).not.toContain('JP');
|
||||
});
|
||||
|
||||
it('AUTH-DB-049: #1490 a country removed in Atlas is not counted on the dashboard either', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { title: 'Tokyo Trip', start_date: PAST_START, end_date: PAST_END });
|
||||
const res = createReservation(testDb, trip.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844);
|
||||
endpoint(res.id, 'to', 1, 35.6762, 139.6503);
|
||||
|
||||
expect(atlas.getTravelStats(user.id).countries).toContain('JP');
|
||||
|
||||
atlas.unmarkCountry(user.id, 'JP');
|
||||
|
||||
const after = atlas.getTravelStats(user.id);
|
||||
expect(after.countries).not.toContain('JP');
|
||||
expect(after.countries).toContain('BE');
|
||||
});
|
||||
|
||||
// ── #1048: the passport card only stamps trips that have happened ──────────
|
||||
// Relative offsets, not literal dates — a hardcoded "future" date expires.
|
||||
const iso = (days: number) => new Date(Date.now() + days * 86_400_000).toISOString().slice(0, 10);
|
||||
|
||||
function placeInRegion(tripId: number, countryCode: string, regionCode: string) {
|
||||
const place = createPlace(testDb, tripId, { name: `Place in ${countryCode}` });
|
||||
testDb
|
||||
.prepare('INSERT OR REPLACE INTO place_regions (place_id, country_code, region_code, region_name) VALUES (?, ?, ?, ?)')
|
||||
.run(place.id, countryCode, regionCode, regionCode);
|
||||
}
|
||||
|
||||
it('AUTH-DB-094: #1048 a place in a future trip does not stamp its country; a past trip does', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const past = createTrip(testDb, user.id, { title: 'Paris, last month', start_date: iso(-40), end_date: iso(-30) });
|
||||
const future = createTrip(testDb, user.id, { title: 'Tokyo, next month', start_date: iso(30), end_date: iso(40) });
|
||||
placeInRegion(past.id, 'FR', 'FR-75');
|
||||
placeInRegion(future.id, 'JP', 'JP-13');
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
|
||||
expect(stats.countries).toContain('FR');
|
||||
expect(stats.countries).not.toContain('JP');
|
||||
});
|
||||
|
||||
it('AUTH-DB-095: #1048 a trip with no dates at all stamps nothing', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const dateless = createTrip(testDb, user.id, { title: 'Someday: Japan' });
|
||||
placeInRegion(dateless.id, 'JP', 'JP-13');
|
||||
const res = createReservation(testDb, dateless.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844); // Brussels
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
|
||||
expect(stats.countries).toEqual([]);
|
||||
});
|
||||
|
||||
it('AUTH-DB-096: #1048 a flight booked for a future trip does not stamp its endpoints', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const future = createTrip(testDb, user.id, { title: 'Tokyo, next month', start_date: iso(30), end_date: iso(40) });
|
||||
const res = createReservation(testDb, future.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844); // Brussels
|
||||
endpoint(res.id, 'to', 1, 35.6762, 139.6503); // Tokyo
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
|
||||
expect(stats.countries).not.toContain('BE');
|
||||
expect(stats.countries).not.toContain('JP');
|
||||
});
|
||||
|
||||
it('AUTH-DB-097: #1048 a manually marked country stays stamped regardless of trip dates', () => {
|
||||
// The manual list is the user's own word, not derived from a trip — the date
|
||||
// filter must not reach it.
|
||||
const { user } = createUser(testDb);
|
||||
const future = createTrip(testDb, user.id, { title: 'Tokyo, next month', start_date: iso(30), end_date: iso(40) });
|
||||
placeInRegion(future.id, 'JP', 'JP-13');
|
||||
testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'JP');
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
|
||||
expect(stats.countries).toContain('JP');
|
||||
});
|
||||
});
|
||||
|
||||
describe('travel-stats quirk fixes', () => {
|
||||
it('AUTH-DB-089: getTravelStats keeps a place at lat 0 / lng 0 (equator, prime meridian)', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { title: 'Null Island' });
|
||||
testDb.prepare('INSERT INTO places (trip_id, name, lat, lng) VALUES (?, ?, ?, ?)').run(trip.id, 'Null Island', 0, 0);
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
expect(stats.coords).toContainEqual({ lat: 0, lng: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* travel-stats.service.test.ts
|
||||
*
|
||||
* getTravelStats lives on AtlasService, but its cases get their own file rather
|
||||
* than joining atlas.service.test.ts. That file carries the admin1 GeoJSON
|
||||
* bundle warm-up in beforeAll (issue #48) and its ATLAS-UNIT-021..024 cases sit
|
||||
* close to the 15s limit under a fully parallel run; adding eighteen more cases
|
||||
* to the same file pushed them over. Same DB-per-file pattern, no shared state.
|
||||
*
|
||||
* The cases moved from auth.service.test.ts with the method; AUTH-DB-* case IDs
|
||||
* are preserved so the history stays greppable.
|
||||
*/
|
||||
|
||||
const { testDb, dbMock } = vi.hoisted(() => {
|
||||
const Database = require('better-sqlite3');
|
||||
const db = new Database(':memory:');
|
||||
db.exec('PRAGMA journal_mode = WAL');
|
||||
db.exec('PRAGMA foreign_keys = ON');
|
||||
db.exec('PRAGMA busy_timeout = 5000');
|
||||
const mock = { db, closeDb: () => {}, reinitialize: () => {}, canAccessTrip: () => undefined, isOwner: () => false };
|
||||
return { testDb: db, dbMock: mock };
|
||||
});
|
||||
|
||||
vi.mock('../../../src/db/database', () => dbMock);
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest';
|
||||
import { createTables } from '../../../src/db/schema';
|
||||
import { runMigrations } from '../../../src/db/migrations';
|
||||
import { resetTestDb } from '../../helpers/test-db';
|
||||
import { createUser, createTrip, createPlace, createReservation } from '../../helpers/factories';
|
||||
import { AtlasService } from '../../../src/nest/atlas/atlas.service';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
|
||||
const atlas = new AtlasService(new DatabaseService(testDb));
|
||||
|
||||
beforeAll(() => { createTables(testDb); runMigrations(testDb); });
|
||||
beforeEach(() => { resetTestDb(testDb); vi.clearAllMocks(); });
|
||||
afterAll(() => { testDb.close(); });
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// getTravelStats — moved here from auth.service.test.ts together with the
|
||||
// method itself. Case IDs preserved so the history stays greppable.
|
||||
// ---------------------------------------------------------------------------
|
||||
// ── getTravelStats — dashboard passport card ────────────────────────────────
|
||||
|
||||
describe('getTravelStats', () => {
|
||||
function endpoint(reservationId: number, role: 'from' | 'to' | 'stop', sequence: number, lat: number, lng: number) {
|
||||
testDb.prepare(
|
||||
'INSERT INTO reservation_endpoints (reservation_id, role, sequence, name, lat, lng) VALUES (?, ?, ?, ?, ?, ?)'
|
||||
).run(reservationId, role, sequence, `Endpoint ${sequence}`, lat, lng);
|
||||
}
|
||||
|
||||
// Every trip below is dated in the past: since #1048 the passport card only counts
|
||||
// countries from trips that have already started, so a dateless fixture would make
|
||||
// these role-filter/tombstone assertions vacuous.
|
||||
const PAST_START = '2023-05-01';
|
||||
const PAST_END = '2023-05-10';
|
||||
|
||||
it('AUTH-DB-047: #1486 counts the from/to countries of a flight', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { title: 'Tokyo Trip', start_date: PAST_START, end_date: PAST_END });
|
||||
const res = createReservation(testDb, trip.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844); // Brussels
|
||||
endpoint(res.id, 'to', 1, 35.6762, 139.6503); // Tokyo
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
expect(stats.countries).toContain('BE');
|
||||
expect(stats.countries).toContain('JP');
|
||||
});
|
||||
|
||||
it('AUTH-DB-048: #1486 a connecting-flight layover does NOT count as visited', () => {
|
||||
// The Atlas query grew a role filter for #1486 but this copy of it did not, so the
|
||||
// dashboard passport card still counted a plane change as a visited country.
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { title: 'Connection Trip', start_date: PAST_START, end_date: PAST_END });
|
||||
const res = createReservation(testDb, trip.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844); // Brussels
|
||||
endpoint(res.id, 'stop', 1, 35.6762, 139.6503); // Tokyo — never leaves the airport
|
||||
endpoint(res.id, 'to', 2, -33.8688, 151.2093); // Sydney
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
expect(stats.countries).toContain('BE');
|
||||
expect(stats.countries).toContain('AU');
|
||||
expect(stats.countries).not.toContain('JP');
|
||||
});
|
||||
|
||||
it('AUTH-DB-049: #1490 a country removed in Atlas is not counted on the dashboard either', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { title: 'Tokyo Trip', start_date: PAST_START, end_date: PAST_END });
|
||||
const res = createReservation(testDb, trip.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844);
|
||||
endpoint(res.id, 'to', 1, 35.6762, 139.6503);
|
||||
|
||||
expect(atlas.getTravelStats(user.id).countries).toContain('JP');
|
||||
|
||||
atlas.unmarkCountry(user.id, 'JP');
|
||||
|
||||
const after = atlas.getTravelStats(user.id);
|
||||
expect(after.countries).not.toContain('JP');
|
||||
expect(after.countries).toContain('BE');
|
||||
});
|
||||
|
||||
// ── #1048: the passport card only stamps trips that have happened ──────────
|
||||
// Relative offsets, not literal dates — a hardcoded "future" date expires.
|
||||
const iso = (days: number) => new Date(Date.now() + days * 86_400_000).toISOString().slice(0, 10);
|
||||
|
||||
function placeInRegion(tripId: number, countryCode: string, regionCode: string) {
|
||||
const place = createPlace(testDb, tripId, { name: `Place in ${countryCode}` });
|
||||
testDb
|
||||
.prepare('INSERT OR REPLACE INTO place_regions (place_id, country_code, region_code, region_name) VALUES (?, ?, ?, ?)')
|
||||
.run(place.id, countryCode, regionCode, regionCode);
|
||||
}
|
||||
|
||||
it('AUTH-DB-094: #1048 a place in a future trip does not stamp its country; a past trip does', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const past = createTrip(testDb, user.id, { title: 'Paris, last month', start_date: iso(-40), end_date: iso(-30) });
|
||||
const future = createTrip(testDb, user.id, { title: 'Tokyo, next month', start_date: iso(30), end_date: iso(40) });
|
||||
placeInRegion(past.id, 'FR', 'FR-75');
|
||||
placeInRegion(future.id, 'JP', 'JP-13');
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
|
||||
expect(stats.countries).toContain('FR');
|
||||
expect(stats.countries).not.toContain('JP');
|
||||
});
|
||||
|
||||
it('AUTH-DB-095: #1048 a trip with no dates at all stamps nothing', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const dateless = createTrip(testDb, user.id, { title: 'Someday: Japan' });
|
||||
placeInRegion(dateless.id, 'JP', 'JP-13');
|
||||
const res = createReservation(testDb, dateless.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844); // Brussels
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
|
||||
expect(stats.countries).toEqual([]);
|
||||
});
|
||||
|
||||
it('AUTH-DB-096: #1048 a flight booked for a future trip does not stamp its endpoints', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const future = createTrip(testDb, user.id, { title: 'Tokyo, next month', start_date: iso(30), end_date: iso(40) });
|
||||
const res = createReservation(testDb, future.id, { type: 'flight' });
|
||||
endpoint(res.id, 'from', 0, 50.9014, 4.4844); // Brussels
|
||||
endpoint(res.id, 'to', 1, 35.6762, 139.6503); // Tokyo
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
|
||||
expect(stats.countries).not.toContain('BE');
|
||||
expect(stats.countries).not.toContain('JP');
|
||||
});
|
||||
|
||||
it('AUTH-DB-097: #1048 a manually marked country stays stamped regardless of trip dates', () => {
|
||||
// The manual list is the user's own word, not derived from a trip — the date
|
||||
// filter must not reach it.
|
||||
const { user } = createUser(testDb);
|
||||
const future = createTrip(testDb, user.id, { title: 'Tokyo, next month', start_date: iso(30), end_date: iso(40) });
|
||||
placeInRegion(future.id, 'JP', 'JP-13');
|
||||
testDb.prepare('INSERT INTO visited_countries (user_id, country_code) VALUES (?, ?)').run(user.id, 'JP');
|
||||
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
|
||||
expect(stats.countries).toContain('JP');
|
||||
});
|
||||
});
|
||||
|
||||
describe('travel-stats quirk fixes', () => {
|
||||
it('AUTH-DB-089: getTravelStats keeps a place at lat 0 / lng 0 (equator, prime meridian)', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id, { title: 'Null Island' });
|
||||
testDb.prepare('INSERT INTO places (trip_id, name, lat, lng) VALUES (?, ?, ?, ?)').run(trip.id, 'Null Island', 0, 0);
|
||||
const stats = atlas.getTravelStats(user.id);
|
||||
expect(stats.coords).toContainEqual({ lat: 0, lng: 0 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user