feat(admin/groups): add groups entity with master-detail sub-routes

Creates the full groups section under /admin/groups/:
- +layout.server.ts: loads groups list via GET /api/groups
- GroupsListPanel.svelte: left list panel (name + permission count, active state)
- +layout.svelte: composes list panel + children slot
- +page.svelte: empty selection prompt
- [id]/+page.server.ts: update (PATCH) and delete actions
- [id]/+page.svelte: edit detail panel with Standard/Administrative permission sections
- new/+page.svelte and +page.server.ts: create group form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-30 01:26:45 +02:00
parent c8a834b91b
commit 8197db2c14
13 changed files with 577 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
import { describe, expect, it, vi, beforeEach } from 'vitest';
import { load } from './+layout.server';
vi.mock('$lib/api.server', () => ({ createApiClient: vi.fn() }));
import { createApiClient } from '$lib/api.server';
function mockApi(groups: unknown[]) {
vi.mocked(createApiClient).mockReturnValue({
GET: vi.fn().mockResolvedValueOnce({ response: { ok: true }, data: groups })
} as ReturnType<typeof createApiClient>);
}
beforeEach(() => vi.clearAllMocks());
describe('admin/groups layout load', () => {
it('returns the groups list', async () => {
mockApi([
{ id: 'g1', name: 'Admins', permissions: ['ADMIN'] },
{ id: 'g2', name: 'Editors', permissions: ['WRITE_ALL'] }
]);
const result = await load({ fetch: vi.fn() as unknown as typeof fetch });
expect(result.groups).toHaveLength(2);
expect(result.groups[0].name).toBe('Admins');
});
it('returns an empty array when the API returns nothing', async () => {
mockApi([]);
const result = await load({ fetch: vi.fn() as unknown as typeof fetch });
expect(result.groups).toEqual([]);
});
it('calls GET /api/groups', async () => {
const mockGet = vi.fn().mockResolvedValue({ response: { ok: true }, data: [] });
vi.mocked(createApiClient).mockReturnValue({ GET: mockGet } as ReturnType<
typeof createApiClient
>);
await load({ fetch: vi.fn() as unknown as typeof fetch });
expect(mockGet).toHaveBeenCalledWith('/api/groups');
});
});