Add backend unit test framework and modularize settings UI

Introduced a Vitest-based backend unit testing framework with supporting scripts, helpers, and GitHub Actions integration. Refactored the admin settings page to a modular architecture, splitting monolithic logic into feature-specific tabs and hooks for improved maintainability and testability. Updated documentation to reflect the new testing setup and settings architecture, and added new dependencies for testing utilities.
This commit is contained in:
kikootwo
2026-01-15 16:49:59 -05:00
parent b3f89d67bb
commit 94dbaf073b
127 changed files with 23549 additions and 2868 deletions
+218
View File
@@ -0,0 +1,218 @@
/**
* Component: Configuration Service Tests
* Documentation: documentation/backend/services/config.md
*/
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createPrismaMock } from '../helpers/prisma';
import { DEFAULT_AUDIBLE_REGION } from '@/lib/types/audible';
const prismaMock = createPrismaMock();
const encryptionMock = vi.hoisted(() => ({
encrypt: vi.fn((value: string) => `enc:${value}`),
decrypt: vi.fn((value: string) => value.replace('enc:', '')),
}));
vi.mock('@/lib/db', () => ({
prisma: prismaMock,
}));
vi.mock('@/lib/services/encryption.service', () => ({
getEncryptionService: () => encryptionMock,
}));
describe('ConfigurationService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('decrypts encrypted values on get', async () => {
prismaMock.configuration.findUnique.mockResolvedValue({
key: 'plex.auth_token',
value: 'enc:secret',
encrypted: true,
});
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
const value = await service.get('plex.auth_token');
expect(value).toBe('secret');
expect(encryptionMock.decrypt).toHaveBeenCalledWith('enc:secret');
});
it('caches values for subsequent get calls', async () => {
prismaMock.configuration.findUnique.mockResolvedValue({
key: 'system.log_level',
value: 'info',
encrypted: false,
});
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
const first = await service.get('system.log_level');
const second = await service.get('system.log_level');
expect(first).toBe('info');
expect(second).toBe('info');
expect(prismaMock.configuration.findUnique).toHaveBeenCalledTimes(1);
});
it('encrypts values when setting encrypted config', async () => {
prismaMock.configuration.upsert.mockResolvedValue({});
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
await service.setMany([
{ key: 'plex.auth_token', value: 'secret', encrypted: true },
]);
expect(encryptionMock.encrypt).toHaveBeenCalledWith('secret');
expect(prismaMock.configuration.upsert).toHaveBeenCalledWith(
expect.objectContaining({
create: expect.objectContaining({
value: 'enc:secret',
encrypted: true,
}),
})
);
});
it('returns default Audible region when not configured', async () => {
prismaMock.configuration.findUnique.mockResolvedValue(null);
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
const region = await service.getAudibleRegion();
expect(region).toBe(DEFAULT_AUDIBLE_REGION);
});
it('returns decrypted values for a category', async () => {
prismaMock.configuration.findMany.mockResolvedValue([
{
key: 'plex.token',
value: 'enc:secret',
encrypted: true,
description: 'Plex token',
},
]);
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
const category = await service.getCategory('plex');
expect(category['plex.token'].value).toBe('secret');
expect(category['plex.token'].encrypted).toBe(true);
});
it('masks encrypted values when listing all config', async () => {
prismaMock.configuration.findMany.mockResolvedValue([
{
key: 'plex.token',
value: 'secret',
encrypted: true,
category: 'plex',
description: 'Plex token',
},
]);
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
const all = await service.getAll();
expect(all['plex.token'].value).toBe('***ENCRYPTED***');
expect(all['plex.token'].category).toBe('plex');
});
it('defaults backend mode to plex when unset', async () => {
prismaMock.configuration.findUnique.mockResolvedValue(null);
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
const mode = await service.getBackendMode();
expect(mode).toBe('plex');
});
it('returns true when audiobookshelf mode is enabled', async () => {
prismaMock.configuration.findUnique.mockResolvedValue({
key: 'system.backend_mode',
value: 'audiobookshelf',
encrypted: false,
});
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
const enabled = await service.isAudiobookshelfMode();
expect(enabled).toBe(true);
});
it('builds Plex config from stored keys', async () => {
prismaMock.configuration.findUnique.mockImplementation(async ({ where: { key } }) => {
const values: Record<string, string> = {
plex_url: 'http://plex',
plex_token: 'token',
plex_audiobook_library_id: 'lib-1',
plex_machine_identifier: 'machine',
};
return values[key]
? { key, value: values[key], encrypted: false }
: null;
});
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
const plexConfig = await service.getPlexConfig();
expect(plexConfig).toEqual({
serverUrl: 'http://plex',
authToken: 'token',
libraryId: 'lib-1',
machineIdentifier: 'machine',
});
});
it('clears cached entries when requested', async () => {
prismaMock.configuration.findUnique.mockResolvedValue({
key: 'system.log_level',
value: 'info',
encrypted: false,
});
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
const first = await service.get('system.log_level');
prismaMock.configuration.findUnique.mockResolvedValue({
key: 'system.log_level',
value: 'debug',
encrypted: false,
});
const cached = await service.get('system.log_level');
service.clearCache('system.log_level');
const updated = await service.get('system.log_level');
expect(first).toBe('info');
expect(cached).toBe('info');
expect(updated).toBe('debug');
});
it('throws when setting configuration fails', async () => {
prismaMock.configuration.upsert.mockRejectedValue(new Error('db failed'));
const { ConfigurationService } = await import('@/lib/services/config.service');
const service = new ConfigurationService();
await expect(
service.setMany([{ key: 'system.test', value: '1' }])
).rejects.toThrow('db failed');
});
});