Unit Tests
All checks were successful
Vercel Production Deployment / Deploy-Production (push) Successful in 1m1s

This commit is contained in:
Ashikagi
2026-03-28 15:54:02 +01:00
parent 6b2d0024ed
commit 0acece98dc
8 changed files with 810 additions and 33 deletions

View File

@@ -0,0 +1,133 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
const importService = async (supabaseMock) => {
vi.resetModules();
vi.doMock('./supabaseClient', () => ({
supabase: supabaseMock
}));
return import('./assessmentShareService');
};
afterEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.doUnmock('./supabaseClient');
delete globalThis.window;
});
describe('assessmentShareService', () => {
it('creates a share link with the current browser URL', async () => {
globalThis.window = {
location: {
origin: 'https://pat.example.com',
pathname: '/manager'
}
};
const rpc = vi.fn().mockResolvedValue({
data: { share_token: 'token-123' },
error: null
});
const { createAssessmentShareLink } = await importService({ rpc });
const result = await createAssessmentShareLink({ id: 'assessment-1' });
expect(rpc).toHaveBeenCalledWith('create_or_get_assessment_share', {
p_assessment_id: 'assessment-1'
});
expect(result).toEqual({
token: 'token-123',
url: 'https://pat.example.com/manager?share=token-123'
});
});
it('maps profile visibility RPC errors to a user-facing German message', async () => {
const rpc = vi.fn().mockResolvedValue({
data: null,
error: { message: 'Tests are hidden in the profile' }
});
const { createAssessmentShareLink } = await importService({ rpc });
await expect(createAssessmentShareLink({ id: 'assessment-1' })).rejects.toThrow(
'Deine Tests sind im Profil aktuell nicht sichtbar.'
);
});
it('lists existing shares by assessment id', async () => {
const inMock = vi.fn().mockResolvedValue({
data: [
{ assessment_id: 'a1', share_token: 'share-a1' },
{ assessment_id: 'a2', share_token: 'share-a2' }
],
error: null
});
const selectMock = vi.fn(() => ({ in: inMock }));
const fromMock = vi.fn(() => ({ select: selectMock }));
const { listAssessmentShares } = await importService({ from: fromMock });
const result = await listAssessmentShares(['a1', 'a2']);
expect(fromMock).toHaveBeenCalledWith('assessment_shares');
expect(selectMock).toHaveBeenCalledWith('assessment_id, share_token');
expect(inMock).toHaveBeenCalledWith('assessment_id', ['a1', 'a2']);
expect(result).toEqual({
a1: 'share-a1',
a2: 'share-a2'
});
});
it('hydrates shared assessments and recalculates derived exercise values', async () => {
const rpc = vi.fn().mockResolvedValue({
data: [
{
id: 'shared-1',
pat_type: 'PAT 2',
datum: '2026-03-28',
name: 'Max Mustermann',
exercises: [
{
name: 'Gerade Einsteiger',
soll: 6,
faktor: 10,
values: ['2', '4', '']
},
{
name: 'Split Drill',
soll: 4,
faktor: 5,
subA: ['1', '3'],
subB: ['5', ''],
subLabels: ['A', 'B']
}
]
}
],
error: null
});
const { getSharedAssessment } = await importService({ rpc });
const result = await getSharedAssessment('share-token');
expect(rpc).toHaveBeenCalledWith('get_shared_assessment', {
p_share_token: 'share-token'
});
expect(result).toMatchObject({
id: 'shared-1',
patType: 'PAT 2',
datum: '2026-03-28',
name: 'Max Mustermann'
});
expect(result.exercises[0]).toMatchObject({
name: 'Gerade Einsteiger',
durchschnitt: 3,
points: 30
});
expect(result.exercises[1]).toMatchObject({
name: 'Split Drill',
durchschnitt: 3,
points: 15,
subLabels: ['A', 'B']
});
});
});

View File

@@ -0,0 +1,171 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
const importService = async (supabaseMock) => {
vi.resetModules();
vi.doMock('./supabaseClient', () => ({
supabase: supabaseMock
}));
return import('./trainingPlanService');
};
afterEach(() => {
vi.clearAllMocks();
vi.resetModules();
vi.doUnmock('./supabaseClient');
});
describe('trainingPlanService', () => {
it('maps sessions to the RPC shape and normalizes task defaults', async () => {
const { mapSessionToRpc } = await importService(null);
const result = mapSessionToRpc({
week_no: '2',
session_no: '3',
main_exercise: 'Break-Aufbau',
secondary_exercises: ['Safety', 'Pattern Play'],
tasks: [
{
type: 'main',
title: 'Hauptblock',
durationMin: '15',
repetitions: 12,
intensity: 'hoch',
instructions: 'Volle Konzentration'
},
{
durationMin: '',
instructions: null
}
],
state: 'done',
notes: 'Saubere Linie'
});
expect(result).toEqual({
weekNo: 2,
sessionNo: 3,
mainExercise: 'Break-Aufbau',
secondaryExercises: ['Safety', 'Pattern Play'],
tasks: [
{
type: 'main',
title: 'Hauptblock',
durationMin: 15,
repetitions: 12,
intensity: 'hoch',
instructions: 'Volle Konzentration'
},
{
type: 'task',
title: '',
durationMin: 0,
repetitions: null,
intensity: null,
instructions: ''
}
],
state: 'done',
notes: 'Saubere Linie'
});
});
it('returns an error result when supabase is not configured', async () => {
const { saveTrainingPlanWithSessions } = await importService(null);
const result = await saveTrainingPlanWithSessions({
patType: 'PAT 1',
analysisSnapshot: {},
durationWeeks: 4,
sessionsPerWeek: 2,
sessions: []
});
expect(result.ok).toBe(false);
expect(result.error.message).toBe('Supabase ist nicht konfiguriert.');
});
it('sends a normalized RPC payload and returns the created plan id', async () => {
const rpc = vi.fn().mockResolvedValue({
data: 'plan-123',
error: null
});
const { saveTrainingPlanWithSessions } = await importService({ rpc });
const result = await saveTrainingPlanWithSessions({
patType: 'PAT 3',
analysisSnapshot: { score: 87 },
durationWeeks: '6',
sessionsPerWeek: '4',
sessions: [
{
weekNo: '1',
sessionNo: '2',
mainExercise: 'Lochen lang',
secondaryExercises: ['Positionsspiel', 'Safety'],
tasks: [
{
type: 'main',
title: 'Serie',
durationMin: '25',
repetitions: 20,
intensity: 'mittel',
instructions: 'Tempo konstant'
}
],
state: 'open',
notes: 'Tag 1'
}
]
});
expect(rpc).toHaveBeenCalledWith('create_training_plan_with_sessions', {
p_pat_type: 'PAT 3',
p_analysis_snapshot: { score: 87 },
p_duration_weeks: 6,
p_sessions_per_week: 4,
p_sessions: [
{
weekNo: 1,
sessionNo: 2,
mainExercise: 'Lochen lang',
secondaryExercises: ['Positionsspiel', 'Safety'],
tasks: [
{
type: 'main',
title: 'Serie',
durationMin: 25,
repetitions: 20,
intensity: 'mittel',
instructions: 'Tempo konstant'
}
],
state: 'open',
notes: 'Tag 1'
}
]
});
expect(result).toEqual({
ok: true,
planId: 'plan-123'
});
});
it('wraps RPC failures into a non-throwing error result', async () => {
const rpc = vi.fn().mockResolvedValue({
data: null,
error: new Error('Datenbank nicht erreichbar')
});
const { saveTrainingPlanWithSessions } = await importService({ rpc });
const result = await saveTrainingPlanWithSessions({
patType: 'PAT 2',
analysisSnapshot: {},
durationWeeks: 2,
sessionsPerWeek: 2,
sessions: []
});
expect(result.ok).toBe(false);
expect(result.error.message).toBe('Datenbank nicht erreichbar');
});
});