// @vitest-environment node import { beforeEach, describe, expect, it, vi } from 'vitest'; import { NextRequest } from 'next/server'; import { createHash } from 'node:crypto'; import { zipSync, strToU8 } from 'fflate'; const mocks = vi.hoisted(() => ({ verify: vi.fn(), get: vi.fn(), doc: vi.fn() })); vi.mock('../lib/firebase-admin', () => ({ getAdminAuth: () => ({ verifyIdToken: mocks.verify }), getAdminFirestore: () => ({ doc: mocks.doc }) })); import { GET } from '../app/api/account/rides/[rideId]/telemetry/route'; const rideId = 'ride_sanitized'; const gpx = ''; const bundle = zipSync({ 'telemetry.gpx': strToU8(gpx), 'ride_summary.json': strToU8('{}') }); const digest = createHash('sha256').update(bundle).digest('hex'); const call = (authorization?: string) => GET(new NextRequest(`https://motorsignal.app/api/account/rides/${rideId}/telemetry`, {headers: authorization ? {authorization} : {}}), {params: Promise.resolve({rideId})}); beforeEach(() => { vi.resetAllMocks(); mocks.verify.mockResolvedValue({uid:'sanitized-owner'}); mocks.doc.mockReturnValue({get:mocks.get}); mocks.get.mockResolvedValue({exists:true,data:()=>({id:rideId,ownerId:'sanitized-owner',telemetryBundlePath:`users/sanitized-owner/rides/${rideId}/telemetry.zip`})}); vi.stubGlobal('fetch',vi.fn().mockResolvedValue(new Response(bundle, {status:200,headers:{'x-motorsignal-ride-id':rideId,'x-motorsignal-sha256':digest}}))); }); describe('owned recorded telemetry API', () => { it('fails closed without authentication before any file/network/Firestore lookup',async()=>{ expect((await call()).status).toBe(401); expect(mocks.get).not.toHaveBeenCalled(); expect(fetch).not.toHaveBeenCalled(); }); it('rejects invalid tokens and a mismatched owner',async()=>{ mocks.verify.mockRejectedValueOnce(Error('invalid')); expect((await call('Bearer invalid')).status).toBe(401); mocks.get.mockResolvedValueOnce({exists:true,data:()=>({ownerId:'another-owner'})}); expect((await call('Bearer valid')).status).toBe(403); expect(fetch).not.toHaveBeenCalled(); }); it('downloads and verifies the selected R2 bundle and returns its actual GPS',async()=>{ const response = await call('Bearer valid'); expect(response.status).toBe(200); const body = await response.json(); expect(body.rideId).toBe(rideId); expect(body.source).toBe('r2-gpx'); expect(body.points.map((p:{latitude:number;longitude:number})=>[p.latitude,p.longitude])).toEqual([[40,-74],[40.001,-73.999]]); expect(mocks.doc).toHaveBeenCalledWith(`users/sanitized-owner/rides/${rideId}`); expect(fetch).toHaveBeenCalledWith(expect.stringContaining(`rideId=${rideId}`),expect.objectContaining({cache:'no-store',headers:expect.objectContaining({Authorization:'Bearer valid'})})); expect(response.headers.get('cache-control')).toContain('no-store'); }); it('rejects a different ride or modified bundle rather than using demo/fallback data',async()=>{ vi.mocked(fetch).mockResolvedValueOnce(new Response(bundle,{headers:{'x-motorsignal-ride-id':'ride_wrong','x-motorsignal-sha256':digest}})); expect((await call('Bearer valid')).status).toBe(422); vi.mocked(fetch).mockResolvedValueOnce(new Response(bundle,{headers:{'x-motorsignal-ride-id':rideId,'x-motorsignal-sha256':'0'.repeat(64)}})); expect((await call('Bearer valid')).status).toBe(422); }); it('keeps inline telemetry separate from R2 and checks ownership for both',async()=>{ mocks.get.mockResolvedValueOnce({exists:true,data:()=>({id:rideId,ownerId:'sanitized-owner',gpxContent:gpx})}); const response=await call('Bearer valid'); expect(response.status).toBe(200); expect((await response.json()).source).toBe('inline-gpx'); expect(fetch).not.toHaveBeenCalled(); }); });