Files
ReadMeABook/tests/services/config.service.test.ts
T
kikootwo 94dbaf073b 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.
2026-01-28 11:41:59 -05:00

219 lines
6.7 KiB
TypeScript

/**
* 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');
});
});