mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Share bounded database pools across API repositories, consolidate sync and chat queries, and make mobile refreshes cache-aware and foreground-aware. Add focused regression coverage, client freshness documentation, and repository agent role configuration.
59 lines
4.1 KiB
TypeScript
59 lines
4.1 KiB
TypeScript
import { Hono } from 'hono';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import type { AuthIdentity } from '../src/auth.js';
|
|
import type { AppDataRepository } from '../src/domains/app-data/repository.js';
|
|
import { registerAppDataRoutes } from '../src/domains/app-data/routes.js';
|
|
|
|
const identity: AuthIdentity = { id: '11111111-1111-4111-8111-111111111111', email: null, name: null, avatarUrl: null };
|
|
const pack = '22222222-2222-4222-8222-222222222222';
|
|
const match = '33333333-3333-4333-8333-333333333333';
|
|
const message = '44444444-4444-4444-8444-444444444444';
|
|
function repository(): AppDataRepository {
|
|
return {
|
|
packContext: vi.fn(async () => ({ id: pack, name: 'Pack', icon: null })),
|
|
packQuestions: vi.fn(async () => []), packTeaser: vi.fn(async () => []),
|
|
matchContext: vi.fn(async () => ({ id: match, question_id: pack, couple_id: pack, match_type: 'yes_yes', created_at: 'now', response_summary: null, question: { id: pack, text: 'Q', partner_text: null }, responses: [] })),
|
|
markMediaViewed: vi.fn(async () => ({ media_viewed_at: 'now', media_expires_at: null })),
|
|
nudgeStatus: vi.fn(async () => ({ last_nudge_sent_at: null })),
|
|
sendNudge: vi.fn(async () => ({ success: true as const, notification_sent: false, reason: 'no_push_token', next_nudge_available_at: 'later' })),
|
|
syncSummary: vi.fn(async () => ({
|
|
server_time: 'now', couple_id: null, profile_updated_at: null, partner_id: null, partner_updated_at: null,
|
|
match_count: 0, new_match_count: 0, latest_match_at: null, pending_yours: 0, pending_theirs: 0,
|
|
unread_total: 0, enabled_packs_fingerprint: '',
|
|
match_state_fingerprint: '', match_unread_fingerprint: '', streak_updated_at: null,
|
|
})),
|
|
getLiveDraw: vi.fn(async () => ({ strokes: [], revision: 0, updated_at: null, updated_by: null })),
|
|
putLiveDraw: vi.fn(async () => ({ strokes: [], revision: 1, updated_at: 'now', updated_by: identity.id })),
|
|
close: vi.fn(async () => undefined),
|
|
};
|
|
}
|
|
function appWith(repo: AppDataRepository) { const app = new Hono<{ Variables: { identity: AuthIdentity } }>(); app.use('/v1/*', async (c, next) => { c.set('identity', identity); await next(); }); registerAppDataRoutes(app, repo); return app; }
|
|
|
|
describe('app data routes', () => {
|
|
it('derives all ownership from the authenticated identity', async () => {
|
|
const repo = repository(); const app = appWith(repo);
|
|
expect((await app.request(`/v1/packs/${pack}/questions`)).status).toBe(200);
|
|
expect(repo.packQuestions).toHaveBeenCalledWith(identity.id, pack);
|
|
expect((await app.request(`/v1/matches/${match}/context`)).status).toBe(200);
|
|
expect(repo.matchContext).toHaveBeenCalledWith(identity.id, match);
|
|
expect((await app.request('/v1/live-draw')).status).toBe(200);
|
|
expect(repo.getLiveDraw).toHaveBeenCalledWith(identity.id);
|
|
expect((await app.request('/v1/me/nudge', { method: 'POST' })).status).toBe(200);
|
|
expect(repo.sendNudge).toHaveBeenCalledWith(identity.id);
|
|
});
|
|
it('validates IDs, media metadata, and complete drawing shape', async () => {
|
|
const repo = repository(); const app = appWith(repo);
|
|
expect((await app.request('/v1/packs/no/questions')).status).toBe(400);
|
|
expect((await app.request(`/v1/messages/${message}/media-viewed`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: '{}' })).status).toBe(400);
|
|
expect((await app.request('/v1/live-draw', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: '{"strokes":[{"id":"x"}]}' })).status).toBe(400);
|
|
expect(repo.putLiveDraw).not.toHaveBeenCalled();
|
|
});
|
|
it('accepts normalized drawing state without a client supplied couple ID', async () => {
|
|
const repo = repository(); const app = appWith(repo);
|
|
const stroke = { id: 's1', userId: identity.id, points: [{ x: 0.2, y: 0.8 }], color: '#fff', width: 2, timestamp: 1, isEraser: false };
|
|
const response = await app.request('/v1/live-draw', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ strokes: [stroke], base_revision: 0 }) });
|
|
expect(response.status).toBe(200);
|
|
expect(repo.putLiveDraw).toHaveBeenCalledWith(identity.id, [stroke], 0);
|
|
});
|
|
});
|