mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 05:11:46 +00:00
fix(airtrail): add back missing tests
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The #1240 write gate: pushReservationToAirtrail must NOT write to AirTrail unless
|
||||||
|
* the flight's owner has opted in (airtrail_write_enabled). Collaborators are mocked
|
||||||
|
* so the test exercises just the gate + payload wiring.
|
||||||
|
*/
|
||||||
|
|
||||||
|
vi.mock('../../../src/db/database', () => ({ db: { prepare: vi.fn() } }));
|
||||||
|
vi.mock('../../../src/services/adminService', () => ({ isAddonEnabled: vi.fn(() => true) }));
|
||||||
|
vi.mock('../../../src/services/auditLog', () => ({ logError: vi.fn(), logInfo: vi.fn() }));
|
||||||
|
vi.mock('../../../src/websocket', () => ({ broadcast: vi.fn() }));
|
||||||
|
vi.mock('../../../src/services/reservationService', () => ({
|
||||||
|
getReservation: vi.fn(),
|
||||||
|
getReservationWithJoins: vi.fn(),
|
||||||
|
updateReservation: vi.fn(),
|
||||||
|
}));
|
||||||
|
vi.mock('../../../src/services/airtrail/airtrailClient', () => ({
|
||||||
|
AirtrailAuthError: class AirtrailAuthError extends Error {},
|
||||||
|
getFlight: vi.fn(),
|
||||||
|
listFlights: vi.fn(),
|
||||||
|
saveFlight: vi.fn(),
|
||||||
|
}));
|
||||||
|
vi.mock('../../../src/services/airtrail/airtrailMapper', () => ({
|
||||||
|
canonicalHash: vi.fn(() => 'hash'),
|
||||||
|
mapFlightToReservation: vi.fn(() => ({})),
|
||||||
|
entityCode: (e: any) => e?.icao || e?.iata || null,
|
||||||
|
}));
|
||||||
|
vi.mock('../../../src/services/airtrail/airtrailService', () => ({
|
||||||
|
isAirtrailWriteEnabled: vi.fn(),
|
||||||
|
getAirtrailCredentials: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { pushReservationToAirtrail } from '../../../src/services/airtrail/airtrailSync';
|
||||||
|
import { db } from '../../../src/db/database';
|
||||||
|
import { getReservationWithJoins } from '../../../src/services/reservationService';
|
||||||
|
import { getFlight, saveFlight } from '../../../src/services/airtrail/airtrailClient';
|
||||||
|
import { isAirtrailWriteEnabled, getAirtrailCredentials } from '../../../src/services/airtrail/airtrailService';
|
||||||
|
|
||||||
|
const linkedRow = { id: 5, trip_id: 9, external_id: '42', external_owner_user_id: 7, sync_enabled: 1 };
|
||||||
|
const runSpy = vi.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
// Route db reads: global sync setting + the linked reservation row.
|
||||||
|
(db.prepare as any).mockImplementation((sql: string) => ({
|
||||||
|
get: () => {
|
||||||
|
if (sql.includes('app_settings')) return { value: 'true' };
|
||||||
|
if (sql.includes('FROM reservations')) return { ...linkedRow };
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
run: (...args: any[]) => {
|
||||||
|
runSpy(sql, args);
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
all: () => [],
|
||||||
|
}));
|
||||||
|
(getAirtrailCredentials as any).mockReturnValue({ baseUrl: 'https://at.example', apiKey: 'k', allowInsecureTls: false });
|
||||||
|
// GET returns AirTrail-owned detail TREK doesn't model — must survive the writeback.
|
||||||
|
(getFlight as any).mockResolvedValue({ id: 42, from: { iata: 'JFK' }, to: { iata: 'LHR' }, seats: [], departureTerminal: '7' });
|
||||||
|
(saveFlight as any).mockResolvedValue({ id: 42 });
|
||||||
|
(getReservationWithJoins as any).mockReturnValue({
|
||||||
|
external_id: '42',
|
||||||
|
reservation_time: '2021-09-01T19:00',
|
||||||
|
reservation_end_time: '2021-09-02T08:00',
|
||||||
|
notes: 'note',
|
||||||
|
metadata: JSON.stringify({}),
|
||||||
|
endpoints: [
|
||||||
|
{ role: 'from', code: 'JFK' },
|
||||||
|
{ role: 'to', code: 'LHR' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pushReservationToAirtrail write gate (#1240)', () => {
|
||||||
|
it('does nothing — and does not detach — when the owner has not opted in', async () => {
|
||||||
|
(isAirtrailWriteEnabled as any).mockReturnValue(false);
|
||||||
|
await pushReservationToAirtrail(5, 9);
|
||||||
|
expect(getFlight).not.toHaveBeenCalled();
|
||||||
|
expect(saveFlight).not.toHaveBeenCalled();
|
||||||
|
expect(runSpy).not.toHaveBeenCalled(); // no detach, no hash write — pure no-op
|
||||||
|
});
|
||||||
|
|
||||||
|
it('writes back, preserving AirTrail-owned fields, when the owner has opted in', async () => {
|
||||||
|
(isAirtrailWriteEnabled as any).mockReturnValue(true);
|
||||||
|
await pushReservationToAirtrail(5, 9);
|
||||||
|
expect(saveFlight).toHaveBeenCalledTimes(1);
|
||||||
|
const payload = (saveFlight as any).mock.calls[0][1];
|
||||||
|
expect(payload.departureTerminal).toBe('7'); // spread preserved the unmanaged field
|
||||||
|
expect(payload.from).toBe('JFK'); // TREK-managed field still applied as a code
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
|
||||||
|
// Avoid any real DNS/network from the SSRF guard during saveSettings.
|
||||||
|
vi.mock('../../../src/utils/ssrfGuard', () => ({
|
||||||
|
checkSsrf: vi.fn(async () => ({ allowed: true, isPrivate: false })),
|
||||||
|
safeFetch: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { db } from '../../../src/db/database';
|
||||||
|
import { createUser } from '../../helpers/factories';
|
||||||
|
import {
|
||||||
|
getConnectionSettings,
|
||||||
|
isAirtrailWriteEnabled,
|
||||||
|
saveSettings,
|
||||||
|
} from '../../../src/services/airtrail/airtrailService';
|
||||||
|
|
||||||
|
describe('airtrail writeback opt-in persistence (#1240)', () => {
|
||||||
|
it('defaults the writeback opt-in to off for a new user', () => {
|
||||||
|
const { user } = createUser(db);
|
||||||
|
expect(isAirtrailWriteEnabled(user.id)).toBe(false);
|
||||||
|
expect(getConnectionSettings(user.id).writeEnabled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('persists the opt-in and lets it be toggled back off without dropping the key', async () => {
|
||||||
|
const { user } = createUser(db);
|
||||||
|
|
||||||
|
await saveSettings(user.id, 'https://at.example.com', 'secret-key', false, true, null);
|
||||||
|
expect(isAirtrailWriteEnabled(user.id)).toBe(true);
|
||||||
|
const on = getConnectionSettings(user.id);
|
||||||
|
expect(on.writeEnabled).toBe(true);
|
||||||
|
expect(on.connected).toBe(true); // key stored
|
||||||
|
|
||||||
|
// No key supplied keeps the stored key; only the opt-in flips back off.
|
||||||
|
await saveSettings(user.id, 'https://at.example.com', undefined, false, false, null);
|
||||||
|
expect(isAirtrailWriteEnabled(user.id)).toBe(false);
|
||||||
|
expect(getConnectionSettings(user.id).connected).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user