test(manacore): add calendar, chat, zitare service tests (score 86→88)

- Calendar service: 6 tests (upcoming events, today, calendars, per-calendar)
- Chat service: 6 tests (recent sort, archived filter, pinned, count, models)
- Zitare service: 6 tests (favorites, random, count, lists)
- Total: 12 test files, 103 tests passing
- Updated audit: testing 65→72, score 86→88, status→production

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-19 21:43:59 +01:00
parent 64c9d49254
commit 77995f2cd3
4 changed files with 429 additions and 8 deletions

View file

@ -1,29 +1,29 @@
---
title: 'ManaCore: Production Readiness Audit'
description: 'Multi-App Ecosystem Dashboard mit 25 Web-Routes, 11 Dashboard-Widgets, 85 Tests, Error Boundary, Onboarding, 5 Sprachen, Mobile App'
description: 'Multi-App Ecosystem Dashboard mit 25 Web-Routes, 11 Dashboard-Widgets, 103 Tests, Error Boundary, Onboarding, 5 Sprachen, Mobile App'
date: 2026-03-19
app: 'manacore'
author: 'Till Schneider'
tags: ['audit', 'manacore', 'production-readiness', 'platform']
score: 86
score: 88
scores:
backend: 55
frontend: 90
database: 70
testing: 65
testing: 72
deployment: 90
documentation: 88
security: 80
ux: 92
status: 'beta'
status: 'production'
version: '0.3.0'
stats:
backendModules: 0
webRoutes: 25
components: 36
dbTables: 0
testFiles: 9
testCount: 85
testFiles: 12
testCount: 103
languages: 5
---
@ -82,14 +82,14 @@ ManaCore ist das **Herzstück des Monorepos** - das Multi-App Ecosystem Dashboar
- Kein lokaler Cache/Offline-Speicher
- Widget-Daten nicht persistiert
## Testing (65/100)
## Testing (72/100)
**Stärken:**
- Vitest konfiguriert mit Coverage (package.json)
- Playwright für E2E Tests eingerichtet
- @vitest/coverage-v8 und @vitest/ui installiert
- 9 Test-Dateien mit 85 Unit Tests:
- 12 Test-Dateien mit 103 Unit Tests:
- Dashboard Widget Registry Tests (14 Tests)
- Default Dashboard Config Tests (12 Tests)
- Base API Client mit Retry-Logik (15 Tests)
@ -99,6 +99,9 @@ ManaCore ist das **Herzstück des Monorepos** - das Multi-App Ecosystem Dashboar
- Contacts Widget Service Tests (10 Tests)
- Storage Widget Service Tests (10 Tests)
- Todo Widget Service Tests (7 Tests)
- Calendar Widget Service Tests (6 Tests)
- Chat Widget Service Tests (6 Tests)
- Zitare Widget Service Tests (6 Tests)
**Lücken:**

View file

@ -0,0 +1,141 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// Mock $app/environment
vi.mock('$app/environment', () => ({
browser: true,
}));
// Mock auth store
vi.mock('$lib/stores/auth.svelte', () => ({
authStore: {
getAccessToken: vi.fn().mockResolvedValue('test-token'),
},
}));
import { calendarService, type CalendarEvent, type Calendar } from './calendar';
describe('calendarService', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('getUpcomingEvents', () => {
it('should fetch upcoming events with date range params', async () => {
const events: CalendarEvent[] = [
{
id: 'e-1',
calendarId: 'cal-1',
userId: 'u-1',
title: 'Meeting',
startTime: '2026-03-20T10:00:00Z',
endTime: '2026-03-20T11:00:00Z',
isAllDay: false,
timezone: 'Europe/Berlin',
status: 'confirmed',
createdAt: '2026-01-01',
updatedAt: '2026-01-01',
},
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ events }),
});
const result = await calendarService.getUpcomingEvents(7);
expect(result.data).toEqual(events);
expect(result.error).toBeNull();
expect(global.fetch).toHaveBeenCalledWith(
expect.stringMatching(/\/events\?startDate=\d{4}-\d{2}-\d{2}&endDate=\d{4}-\d{2}-\d{2}/),
expect.any(Object)
);
});
it('should return empty array when no events', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ events: [] }),
});
const result = await calendarService.getUpcomingEvents();
expect(result.data).toEqual([]);
});
it('should return error on network failure', async () => {
global.fetch = vi.fn().mockRejectedValue(new Error('Failed to fetch'));
const result = await calendarService.getUpcomingEvents();
expect(result.data).toBeNull();
expect(result.error).toBeTruthy();
});
});
describe('getTodayEvents', () => {
it('should fetch events for today', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ events: [{ id: 'e-1', title: 'Today Event' }] }),
});
const result = await calendarService.getTodayEvents();
expect(result.data).toHaveLength(1);
// Both startDate and endDate should be the same (today)
const url = (global.fetch as any).mock.calls[0][0];
const params = new URL(url).searchParams;
expect(params.get('startDate')).toBe(params.get('endDate'));
});
});
describe('getCalendars', () => {
it('should fetch all calendars', async () => {
const calendars: Calendar[] = [
{
id: 'cal-1',
userId: 'u-1',
name: 'Work',
color: '#3B82F6',
isDefault: true,
isVisible: true,
timezone: 'Europe/Berlin',
createdAt: '2026-01-01',
updatedAt: '2026-01-01',
},
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ calendars }),
});
const result = await calendarService.getCalendars();
expect(result.data).toEqual(calendars);
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/calendars'),
expect.any(Object)
);
});
});
describe('getCalendarEvents', () => {
it('should fetch events for specific calendar', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ events: [] }),
});
await calendarService.getCalendarEvents('cal-1', 14);
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('calendarIds=cal-1'),
expect.any(Object)
);
});
});
});

View file

@ -0,0 +1,145 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// Mock $app/environment
vi.mock('$app/environment', () => ({
browser: true,
}));
// Mock auth store
vi.mock('$lib/stores/auth.svelte', () => ({
authStore: {
getAccessToken: vi.fn().mockResolvedValue('test-token'),
},
}));
import { chatService, type Conversation } from './chat';
const mockConversation = (overrides: Partial<Conversation> = {}): Conversation => ({
id: 'conv-1',
userId: 'u-1',
title: 'Test Chat',
modelId: 'gpt-4',
conversationMode: 'free',
documentMode: false,
isArchived: false,
isPinned: false,
createdAt: '2026-01-01',
updatedAt: '2026-03-01',
...overrides,
});
describe('chatService', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('getRecentConversations', () => {
it('should fetch and sort conversations by updatedAt', async () => {
const conversations = [
mockConversation({ id: 'c-1', updatedAt: '2026-01-01' }),
mockConversation({ id: 'c-2', updatedAt: '2026-03-01' }),
mockConversation({ id: 'c-3', updatedAt: '2026-02-01' }),
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(conversations),
});
const result = await chatService.getRecentConversations(5);
expect(result.data).toHaveLength(3);
expect(result.data![0].id).toBe('c-2'); // Most recent first
expect(result.data![1].id).toBe('c-3');
expect(result.data![2].id).toBe('c-1');
});
it('should filter out archived conversations', async () => {
const conversations = [
mockConversation({ id: 'c-1', isArchived: false }),
mockConversation({ id: 'c-2', isArchived: true }),
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(conversations),
});
const result = await chatService.getRecentConversations();
expect(result.data).toHaveLength(1);
expect(result.data![0].id).toBe('c-1');
});
it('should respect limit parameter', async () => {
const conversations = Array.from({ length: 10 }, (_, i) =>
mockConversation({ id: `c-${i}`, updatedAt: `2026-03-${String(i + 1).padStart(2, '0')}` })
);
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(conversations),
});
const result = await chatService.getRecentConversations(3);
expect(result.data).toHaveLength(3);
});
});
describe('getPinnedConversations', () => {
it('should return only pinned non-archived conversations', async () => {
const conversations = [
mockConversation({ id: 'c-1', isPinned: true, isArchived: false }),
mockConversation({ id: 'c-2', isPinned: false }),
mockConversation({ id: 'c-3', isPinned: true, isArchived: true }),
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(conversations),
});
const result = await chatService.getPinnedConversations();
expect(result.data).toHaveLength(1);
expect(result.data![0].id).toBe('c-1');
});
});
describe('getConversationCount', () => {
it('should count active and pinned conversations', async () => {
const conversations = [
mockConversation({ isPinned: true, isArchived: false }),
mockConversation({ isPinned: false, isArchived: false }),
mockConversation({ isPinned: true, isArchived: true }),
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(conversations),
});
const result = await chatService.getConversationCount();
expect(result.data).toEqual({ total: 2, pinned: 1 });
});
});
describe('getModels', () => {
it('should fetch AI models', async () => {
const models = [{ id: 'm-1', name: 'GPT-4', description: 'Advanced model' }];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(models),
});
const result = await chatService.getModels();
expect(result.data).toEqual(models);
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/chat/models'),
expect.any(Object)
);
});
});
});

View file

@ -0,0 +1,132 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// Mock $app/environment
vi.mock('$app/environment', () => ({
browser: true,
}));
// Mock auth store
vi.mock('$lib/stores/auth.svelte', () => ({
authStore: {
getAccessToken: vi.fn().mockResolvedValue('test-token'),
},
}));
import { zitareService, type Favorite } from './zitare';
describe('zitareService', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('getFavorites', () => {
it('should fetch favorites', async () => {
const favorites: Favorite[] = [
{ id: 'f-1', userId: 'u-1', quoteId: 'q-1', createdAt: '2026-01-01' },
{ id: 'f-2', userId: 'u-1', quoteId: 'q-2', createdAt: '2026-02-01' },
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(favorites),
});
const result = await zitareService.getFavorites();
expect(result.data).toEqual(favorites);
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/favorites'),
expect.any(Object)
);
});
});
describe('getRandomFavorite', () => {
it('should return a random favorite from the list', async () => {
const favorites: Favorite[] = [
{ id: 'f-1', userId: 'u-1', quoteId: 'q-1', createdAt: '2026-01-01' },
{ id: 'f-2', userId: 'u-1', quoteId: 'q-2', createdAt: '2026-02-01' },
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(favorites),
});
const result = await zitareService.getRandomFavorite();
expect(result.data).toBeTruthy();
expect(result.error).toBeNull();
expect(favorites.map((f) => f.id)).toContain(result.data!.id);
});
it('should return error when no favorites exist', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve([]),
});
const result = await zitareService.getRandomFavorite();
expect(result.data).toBeNull();
expect(result.error).toBe('No favorites found');
});
});
describe('getFavoriteCount', () => {
it('should return the count of favorites', async () => {
const favorites = [
{ id: 'f-1', userId: 'u-1', quoteId: 'q-1', createdAt: '2026-01-01' },
{ id: 'f-2', userId: 'u-1', quoteId: 'q-2', createdAt: '2026-02-01' },
{ id: 'f-3', userId: 'u-1', quoteId: 'q-3', createdAt: '2026-03-01' },
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(favorites),
});
const result = await zitareService.getFavoriteCount();
expect(result.data).toBe(3);
expect(result.error).toBeNull();
});
it('should return error on network failure', async () => {
global.fetch = vi.fn().mockRejectedValue(new Error('Failed to fetch'));
const result = await zitareService.getFavoriteCount();
expect(result.data).toBeNull();
expect(result.error).toBeTruthy();
});
});
describe('getLists', () => {
it('should fetch quote lists', async () => {
const lists = [
{
id: 'l-1',
userId: 'u-1',
name: 'Motivation',
quoteIds: ['q-1', 'q-2'],
createdAt: '2026-01-01',
updatedAt: '2026-01-01',
},
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve(lists),
});
const result = await zitareService.getLists();
expect(result.data).toEqual(lists);
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('/lists'),
expect.any(Object)
);
});
});
});