Add mobile services, web proxy, and Gemma4 agent loop

- Mobile: add push notification and calendar sync services with full test suite (calendar, eventStore, notifications,
  screens)
- Mobile: add EAS build config and Jest setup
- Web: add API proxy, update useDestinationStation/useJourneys hooks, add middleware tests
- Web: update next.config and rebuild
- Agent: add Gemma4-based agent loop (agent_base, ttl_agent, ts_agent)
- Docs: add privacy policy, post-MVP plan, and aider rules
This commit is contained in:
2026-05-11 18:32:54 +02:00
parent 20159262c1
commit 5bcfafcbaf
120 changed files with 8626 additions and 860 deletions
+94
View File
@@ -0,0 +1,94 @@
import { NextRequest } from 'next/server';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import proxy from '../proxy';
// Mock NextResponse to avoid internal routing context issues
vi.mock('next/server', async (importOriginal) => {
const actual = await importOriginal()
// Create a mock constructor class with static methods
class MockNextResponse {
public headers: Headers;
public status: number;
public statusText: string;
public body: BodyInit | null;
static next: () => MockNextResponse;
static json: () => void;
static redirect: () => void;
constructor(_init?: ResponseInit) {
this.headers = new Headers();
this.status = _init?.status ?? 200;
this.statusText = 'OK';
this.body = null;
}
clone() { return new MockNextResponse(); }
arrayBuffer() { return new ArrayBuffer(0); }
blob() { return new Blob(); }
formData() { return new FormData(); }
json() { return {}; }
text() { return ''; }
}
// Static methods
MockNextResponse.next = vi.fn(() => new MockNextResponse());
MockNextResponse.json = vi.fn();
MockNextResponse.redirect = vi.fn();
return {
...actual as Record<string, unknown>,
NextResponse: MockNextResponse,
}
});
describe('middleware', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should bypass non-API requests', () => {
const request = new NextRequest('http://localhost:3000/test', {
headers: { origin: 'http://localhost:3000' },
});
const response = proxy(request);
expect(response).toBeDefined();
});
it('should handle preflight requests for API routes', () => {
const request = new NextRequest('http://localhost:3000/api/test', {
method: 'OPTIONS',
headers: { origin: 'http://localhost:3000' },
});
const response = proxy(request);
expect(response.status).toBe(200);
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
expect(response.headers.get('Access-Control-Allow-Methods')).toBe('GET, POST, PUT, DELETE, OPTIONS');
expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type, Authorization');
expect(response.headers.get('Access-Control-Max-Age')).toBe('86400');
expect(response.headers.get('Vary')).toBe('Origin');
});
it('should handle regular API requests with cross-origin', () => {
const request = new NextRequest('http://localhost:3000/api/test', {
headers: { origin: 'http://different-origin.com' },
});
const response = proxy(request);
expect(response).toBeDefined();
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
expect(response.headers.get('Vary')).toBe('Origin');
});
it('should handle API requests without Origin header', () => {
const request = new NextRequest('http://localhost:3000/api/test', {
headers: {},
});
const response = proxy(request);
expect(response).toBeDefined();
});
});