import { afterEach, describe, expect, it } from "vitest"; import { openDatabase, type CompanionDatabase } from "../src/database.js"; import { TripStore } from "../src/trips.js"; let database: CompanionDatabase | null = null; function setup() { database = openDatabase(":memory:"); database.prepare( `INSERT INTO mobile_devices (id, name, token_hash, created_at, last_seen_at) VALUES ('phone', 'Test phone', 'token', '2026-08-01T10:00:00.000Z', '2026-08-01T10:00:00.000Z')` ).run(); return new TripStore(database); } function location(capturedAt: string, longitude: number, speedKph: number) { return { deviceId: "phone", capturedAt, latitude: 55.6761, longitude, accuracyMeters: 4, speedKph, phoneBatteryPercent: 80 }; } afterEach(() => { database?.close(); database = null; }); describe("trip recording", () => { it("starts on movement, records a route, and ends after stationary timeout", () => { const trips = setup(); expect(trips.record(location("2026-08-01T10:00:00.000Z", 12.5683, 0))).toEqual({ tripId: null, active: false }); const started = trips.record(location("2026-08-01T10:00:10.000Z", 12.5684, 20)); expect(started.active).toBe(true); trips.record(location("2026-08-01T10:01:10.000Z", 12.5784, 45)); trips.record(location("2026-08-01T10:04:11.000Z", 12.5784, 0)); const list = trips.list().trips; expect(list).toHaveLength(1); expect(list[0]).toMatchObject({ active: false, pointCount: 2, peakSpeedKph: 45 }); expect(list[0]!.distanceKm).toBeGreaterThan(0.5); const detail = trips.detail(started.tripId!); expect(detail?.points).toHaveLength(2); expect(detail?.pins).toEqual(expect.arrayContaining([ expect.objectContaining({ type: "start" }), expect.objectContaining({ type: "peak-speed", speedKph: 45 }), expect.objectContaining({ type: "end" }) ])); }); it("uses nearby vehicle telemetry for speed and battery statistics", () => { const trips = setup(); const first = Date.parse("2026-08-01T12:00:00.000Z"); database!.prepare( `INSERT INTO car_telemetry_samples (recorded_at, speed_kph, battery_percent, total_consumption_kwh) VALUES (?, 60, 78, 12.4), (?, 40, 74, 14.1)` ).run(first, first + 60_000); const started = trips.record(location("2026-08-01T12:00:00.000Z", 12.5683, 0)); trips.record(location("2026-08-01T12:01:00.000Z", 12.5783, 0)); const detail = trips.detail(started.tripId!); expect(detail).toMatchObject({ peakSpeedKph: 60, vehicleBatteryStartPercent: 78, vehicleBatteryEndPercent: 74, batteryConsumedPercent: 4, energyConsumedKwh: expect.closeTo(1.7, 5) }); }); it("does not create trips from inaccurate or stationary locations", () => { const trips = setup(); trips.record({ ...location("2026-08-01T13:00:00.000Z", 12.5, 80), accuracyMeters: 250 }); trips.record(location("2026-08-01T13:01:00.000Z", 12.5, 2)); expect(trips.list().trips).toEqual([]); }); });