2024-03-20 19:32:04 +01:00
|
|
|
import { BadRequestException } from '@nestjs/common';
|
2024-09-27 10:28:42 -04:00
|
|
|
import { defaults, SystemConfig } from 'src/config';
|
2023-07-08 22:43:11 -04:00
|
|
|
import {
|
|
|
|
|
AudioCodec,
|
2024-03-20 19:32:04 +01:00
|
|
|
Colorspace,
|
2024-09-27 10:28:42 -04:00
|
|
|
CQMode,
|
2024-04-02 00:56:56 -04:00
|
|
|
ImageFormat,
|
2023-12-14 11:55:40 -05:00
|
|
|
LogLevel,
|
2023-08-07 16:35:25 -04:00
|
|
|
ToneMapping,
|
2023-08-01 21:56:10 -04:00
|
|
|
TranscodeHWAccel,
|
2023-07-08 22:43:11 -04:00
|
|
|
TranscodePolicy,
|
|
|
|
|
VideoCodec,
|
2024-07-21 17:14:23 -04:00
|
|
|
VideoContainer,
|
2024-09-27 10:28:42 -04:00
|
|
|
} from 'src/enum';
|
2024-09-30 10:35:11 -04:00
|
|
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
2024-03-21 12:59:49 +01:00
|
|
|
import { QueueName } from 'src/interfaces/job.interface';
|
2024-03-21 00:07:30 +01:00
|
|
|
import { SystemConfigService } from 'src/services/system-config.service';
|
2025-02-07 17:26:49 -05:00
|
|
|
import { DeepPartial, IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types';
|
2024-10-02 10:54:35 -04:00
|
|
|
import { mockEnvData } from 'test/repositories/config.repository.mock';
|
|
|
|
|
import { newTestService } from 'test/utils';
|
2024-04-17 03:00:31 +05:30
|
|
|
import { Mocked } from 'vitest';
|
2023-01-21 11:11:55 -05:00
|
|
|
|
2024-05-15 18:58:23 -04:00
|
|
|
const partialConfig = {
|
|
|
|
|
ffmpeg: { crf: 30 },
|
|
|
|
|
oauth: { autoLaunch: true },
|
|
|
|
|
trash: { days: 10 },
|
|
|
|
|
user: { deleteDelay: 15 },
|
|
|
|
|
} satisfies DeepPartial<SystemConfig>;
|
2023-01-21 11:11:55 -05:00
|
|
|
|
2023-06-01 06:32:51 -04:00
|
|
|
const updatedConfig = Object.freeze<SystemConfig>({
|
|
|
|
|
job: {
|
|
|
|
|
[QueueName.BACKGROUND_TASK]: { concurrency: 5 },
|
2023-12-16 11:50:46 -05:00
|
|
|
[QueueName.SMART_SEARCH]: { concurrency: 2 },
|
2023-06-01 06:32:51 -04:00
|
|
|
[QueueName.METADATA_EXTRACTION]: { concurrency: 5 },
|
2024-01-18 00:08:48 -05:00
|
|
|
[QueueName.FACE_DETECTION]: { concurrency: 2 },
|
2023-06-01 06:32:51 -04:00
|
|
|
[QueueName.SEARCH]: { concurrency: 5 },
|
|
|
|
|
[QueueName.SIDECAR]: { concurrency: 5 },
|
2023-10-11 00:59:13 +02:00
|
|
|
[QueueName.LIBRARY]: { concurrency: 5 },
|
2023-09-25 17:07:21 +02:00
|
|
|
[QueueName.MIGRATION]: { concurrency: 5 },
|
2024-06-05 11:45:53 +01:00
|
|
|
[QueueName.THUMBNAIL_GENERATION]: { concurrency: 3 },
|
2023-06-01 06:32:51 -04:00
|
|
|
[QueueName.VIDEO_CONVERSION]: { concurrency: 1 },
|
2024-05-02 16:43:18 +02:00
|
|
|
[QueueName.NOTIFICATION]: { concurrency: 5 },
|
2023-06-01 06:32:51 -04:00
|
|
|
},
|
2024-10-31 11:29:42 +00:00
|
|
|
backup: {
|
|
|
|
|
database: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
cronExpression: '0 02 * * *',
|
|
|
|
|
keepLastAmount: 14,
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-01-21 11:11:55 -05:00
|
|
|
ffmpeg: {
|
2023-05-22 14:07:43 -04:00
|
|
|
crf: 30,
|
|
|
|
|
threads: 0,
|
2023-01-21 11:11:55 -05:00
|
|
|
preset: 'ultrafast',
|
2023-07-08 22:43:11 -04:00
|
|
|
targetAudioCodec: AudioCodec.AAC,
|
2024-10-18 13:26:16 +01:00
|
|
|
acceptedAudioCodecs: [AudioCodec.AAC, AudioCodec.MP3, AudioCodec.LIBOPUS, AudioCodec.PCMS16LE],
|
2023-04-04 02:42:53 +01:00
|
|
|
targetResolution: '720',
|
2023-07-08 22:43:11 -04:00
|
|
|
targetVideoCodec: VideoCodec.H264,
|
2024-01-26 18:02:56 +01:00
|
|
|
acceptedVideoCodecs: [VideoCodec.H264],
|
2024-07-21 17:14:23 -04:00
|
|
|
acceptedContainers: [VideoContainer.MOV, VideoContainer.OGG, VideoContainer.WEBM],
|
2023-05-22 14:07:43 -04:00
|
|
|
maxBitrate: '0',
|
2023-09-02 21:22:42 -04:00
|
|
|
bframes: -1,
|
|
|
|
|
refs: 0,
|
|
|
|
|
gopSize: 0,
|
|
|
|
|
temporalAQ: false,
|
|
|
|
|
cqMode: CQMode.AUTO,
|
2023-05-22 14:07:43 -04:00
|
|
|
twoPass: false,
|
2024-01-30 02:40:02 +01:00
|
|
|
preferredHwDevice: 'auto',
|
2023-07-08 22:43:11 -04:00
|
|
|
transcode: TranscodePolicy.REQUIRED,
|
2023-08-01 21:56:10 -04:00
|
|
|
accel: TranscodeHWAccel.DISABLED,
|
2024-05-16 13:30:26 -04:00
|
|
|
accelDecode: false,
|
2023-08-07 16:35:25 -04:00
|
|
|
tonemap: ToneMapping.HABLE,
|
2023-01-21 11:11:55 -05:00
|
|
|
},
|
2023-12-14 11:55:40 -05:00
|
|
|
logging: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
level: LogLevel.LOG,
|
|
|
|
|
},
|
2024-09-05 00:23:58 +02:00
|
|
|
metadata: {
|
|
|
|
|
faces: {
|
|
|
|
|
import: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-08-25 00:15:03 -04:00
|
|
|
machineLearning: {
|
|
|
|
|
enabled: true,
|
2024-12-04 15:17:47 -05:00
|
|
|
urls: ['http://immich-machine-learning:3003'],
|
2023-08-29 09:58:00 -04:00
|
|
|
clip: {
|
|
|
|
|
enabled: true,
|
2023-10-31 06:02:04 -04:00
|
|
|
modelName: 'ViT-B-32__openai',
|
2023-08-29 09:58:00 -04:00
|
|
|
},
|
2024-05-16 13:08:37 -04:00
|
|
|
duplicateDetection: {
|
2024-05-27 07:51:41 +07:00
|
|
|
enabled: true,
|
2024-06-30 17:36:02 +02:00
|
|
|
maxDistance: 0.01,
|
2024-05-16 13:08:37 -04:00
|
|
|
},
|
2023-08-29 09:58:00 -04:00
|
|
|
facialRecognition: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
modelName: 'buffalo_l',
|
|
|
|
|
minScore: 0.7,
|
2024-03-06 22:20:38 -05:00
|
|
|
maxDistance: 0.5,
|
2024-01-18 00:08:48 -05:00
|
|
|
minFaces: 3,
|
2023-08-29 09:58:00 -04:00
|
|
|
},
|
2023-08-25 00:15:03 -04:00
|
|
|
},
|
2023-09-08 22:51:46 -04:00
|
|
|
map: {
|
|
|
|
|
enabled: true,
|
2024-09-23 21:30:23 +01:00
|
|
|
lightStyle: 'https://tiles.immich.cloud/v1/style/light.json',
|
|
|
|
|
darkStyle: 'https://tiles.immich.cloud/v1/style/dark.json',
|
2023-09-08 22:51:46 -04:00
|
|
|
},
|
2023-09-26 09:03:57 +02:00
|
|
|
reverseGeocoding: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
},
|
2023-01-21 11:11:55 -05:00
|
|
|
oauth: {
|
|
|
|
|
autoLaunch: true,
|
|
|
|
|
autoRegister: true,
|
|
|
|
|
buttonText: 'Login with OAuth',
|
|
|
|
|
clientId: '',
|
|
|
|
|
clientSecret: '',
|
2024-03-01 19:46:07 -05:00
|
|
|
defaultStorageQuota: 0,
|
2023-01-21 11:11:55 -05:00
|
|
|
enabled: false,
|
|
|
|
|
issuerUrl: '',
|
|
|
|
|
mobileOverrideEnabled: false,
|
|
|
|
|
mobileRedirectUri: '',
|
|
|
|
|
scope: 'openid email profile',
|
2024-02-02 06:27:54 +01:00
|
|
|
signingAlgorithm: 'RS256',
|
2024-07-11 07:55:00 -04:00
|
|
|
profileSigningAlgorithm: 'none',
|
2023-07-15 15:50:29 -04:00
|
|
|
storageLabelClaim: 'preferred_username',
|
2024-03-01 19:46:07 -05:00
|
|
|
storageQuotaClaim: 'immich_quota',
|
2023-01-21 11:11:55 -05:00
|
|
|
},
|
|
|
|
|
passwordLogin: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
},
|
2024-01-03 21:54:48 -05:00
|
|
|
server: {
|
|
|
|
|
externalDomain: '',
|
2024-01-04 00:00:17 -05:00
|
|
|
loginPageMessage: '',
|
2024-11-26 10:51:01 -05:00
|
|
|
publicUsers: true,
|
2024-01-03 21:54:48 -05:00
|
|
|
},
|
2023-01-21 11:11:55 -05:00
|
|
|
storageTemplate: {
|
2023-12-29 18:41:33 +00:00
|
|
|
enabled: false,
|
|
|
|
|
hashVerificationEnabled: true,
|
2023-01-21 11:11:55 -05:00
|
|
|
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
|
|
|
|
},
|
2024-04-02 00:56:56 -04:00
|
|
|
image: {
|
2024-09-28 02:01:04 -04:00
|
|
|
thumbnail: {
|
|
|
|
|
size: 250,
|
|
|
|
|
format: ImageFormat.WEBP,
|
|
|
|
|
quality: 80,
|
|
|
|
|
},
|
|
|
|
|
preview: {
|
|
|
|
|
size: 1440,
|
|
|
|
|
format: ImageFormat.JPEG,
|
|
|
|
|
quality: 80,
|
|
|
|
|
},
|
2023-09-03 02:21:51 -04:00
|
|
|
colorspace: Colorspace.P3,
|
2024-04-19 11:50:13 -04:00
|
|
|
extractEmbedded: false,
|
2023-08-08 09:39:51 -05:00
|
|
|
},
|
2023-10-24 17:05:42 +02:00
|
|
|
newVersionCheck: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
},
|
2023-10-06 07:01:14 +00:00
|
|
|
trash: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
days: 10,
|
|
|
|
|
},
|
2023-10-23 11:38:41 -07:00
|
|
|
theme: {
|
|
|
|
|
customCss: '',
|
|
|
|
|
},
|
2023-10-31 21:19:12 +01:00
|
|
|
library: {
|
|
|
|
|
scan: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
cronExpression: '0 0 * * *',
|
|
|
|
|
},
|
2024-01-31 09:15:54 +01:00
|
|
|
watch: {
|
|
|
|
|
enabled: false,
|
|
|
|
|
},
|
2023-10-31 21:19:12 +01:00
|
|
|
},
|
2024-03-06 00:45:40 -05:00
|
|
|
user: {
|
|
|
|
|
deleteDelay: 15,
|
|
|
|
|
},
|
2024-05-02 16:43:18 +02:00
|
|
|
notifications: {
|
|
|
|
|
smtp: {
|
|
|
|
|
enabled: false,
|
|
|
|
|
from: '',
|
|
|
|
|
replyTo: '',
|
|
|
|
|
transport: {
|
|
|
|
|
host: '',
|
|
|
|
|
port: 587,
|
|
|
|
|
username: '',
|
|
|
|
|
password: '',
|
|
|
|
|
ignoreCert: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-12-04 21:26:02 +01:00
|
|
|
templates: {
|
|
|
|
|
email: {
|
|
|
|
|
albumInviteTemplate: '',
|
|
|
|
|
welcomeTemplate: '',
|
|
|
|
|
albumUpdateTemplate: '',
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-01-21 11:11:55 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe(SystemConfigService.name, () => {
|
|
|
|
|
let sut: SystemConfigService;
|
2024-10-02 10:54:35 -04:00
|
|
|
|
2024-10-01 16:03:55 -04:00
|
|
|
let configMock: Mocked<IConfigRepository>;
|
2024-04-16 10:44:45 -04:00
|
|
|
let eventMock: Mocked<IEventRepository>;
|
2025-01-23 08:31:30 -05:00
|
|
|
let loggerMock: Mocked<ILoggingRepository>;
|
2024-10-02 10:54:35 -04:00
|
|
|
let systemMock: Mocked<ISystemMetadataRepository>;
|
2023-01-21 11:11:55 -05:00
|
|
|
|
2024-03-05 23:23:06 +01:00
|
|
|
beforeEach(() => {
|
2024-10-02 10:54:35 -04:00
|
|
|
({ sut, configMock, eventMock, loggerMock, systemMock } = newTestService(SystemConfigService));
|
2023-01-21 11:11:55 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work', () => {
|
|
|
|
|
expect(sut).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('getDefaults', () => {
|
|
|
|
|
it('should return the default config', () => {
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.get.mockResolvedValue(partialConfig);
|
2023-01-21 11:11:55 -05:00
|
|
|
|
2023-07-15 00:03:56 -04:00
|
|
|
expect(sut.getDefaults()).toEqual(defaults);
|
2024-05-15 18:58:23 -04:00
|
|
|
expect(systemMock.get).not.toHaveBeenCalled();
|
2023-01-21 11:11:55 -05:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('getConfig', () => {
|
|
|
|
|
it('should return the default config', async () => {
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.get.mockResolvedValue({});
|
2023-01-21 11:11:55 -05:00
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
await expect(sut.getSystemConfig()).resolves.toEqual(defaults);
|
2023-01-21 11:11:55 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should merge the overrides', async () => {
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.get.mockResolvedValue({
|
|
|
|
|
ffmpeg: { crf: 30 },
|
|
|
|
|
oauth: { autoLaunch: true },
|
|
|
|
|
trash: { days: 10 },
|
|
|
|
|
user: { deleteDelay: 15 },
|
|
|
|
|
});
|
2023-01-21 11:11:55 -05:00
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig);
|
2023-01-21 11:11:55 -05:00
|
|
|
});
|
2023-08-25 19:44:52 +02:00
|
|
|
|
2024-03-12 16:29:49 +01:00
|
|
|
it('should load the config from a json file', async () => {
|
2024-10-01 16:03:55 -04:00
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify(partialConfig));
|
2023-08-25 19:44:52 +02:00
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig);
|
2023-08-25 19:44:52 +02:00
|
|
|
|
2024-05-15 18:58:23 -04:00
|
|
|
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
|
2023-08-25 19:44:52 +02:00
|
|
|
});
|
|
|
|
|
|
2024-10-29 11:59:35 -04:00
|
|
|
it('should transform booleans', async () => {
|
|
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
|
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { twoPass: 'false' } }));
|
|
|
|
|
|
|
|
|
|
await expect(sut.getSystemConfig()).resolves.toMatchObject({
|
|
|
|
|
ffmpeg: expect.objectContaining({ twoPass: false }),
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should transform numbers', async () => {
|
|
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
|
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { threads: '42' } }));
|
|
|
|
|
|
|
|
|
|
await expect(sut.getSystemConfig()).resolves.toMatchObject({
|
|
|
|
|
ffmpeg: expect.objectContaining({ threads: 42 }),
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-07 12:27:52 -05:00
|
|
|
it('should accept valid cron expressions', async () => {
|
|
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
|
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify({ library: { scan: { cronExpression: '0 0 * * *' } } }));
|
|
|
|
|
|
|
|
|
|
await expect(sut.getSystemConfig()).resolves.toMatchObject({
|
|
|
|
|
library: {
|
|
|
|
|
scan: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
cronExpression: '0 0 * * *',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should reject invalid cron expressions', async () => {
|
|
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
|
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify({ library: { scan: { cronExpression: 'foo' } } }));
|
|
|
|
|
|
|
|
|
|
await expect(sut.getSystemConfig()).rejects.toThrow(
|
|
|
|
|
'library.scan.cronExpression has failed the following constraints: cronValidator',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2024-05-24 16:44:50 -04:00
|
|
|
it('should log errors with the config file', async () => {
|
2024-10-01 16:03:55 -04:00
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
2024-05-24 16:44:50 -04:00
|
|
|
|
|
|
|
|
systemMock.readFile.mockResolvedValue(`{ "ffmpeg2": true, "ffmpeg2": true }`);
|
|
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
await expect(sut.getSystemConfig()).rejects.toBeInstanceOf(Error);
|
2024-05-24 16:44:50 -04:00
|
|
|
|
|
|
|
|
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
|
|
|
|
|
expect(loggerMock.error).toHaveBeenCalledTimes(2);
|
|
|
|
|
expect(loggerMock.error.mock.calls[0][0]).toEqual('Unable to load configuration file: immich-config.json');
|
|
|
|
|
expect(loggerMock.error.mock.calls[1][0].toString()).toEqual(
|
|
|
|
|
expect.stringContaining('YAMLException: duplicated mapping key (1:20)'),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2024-03-12 16:29:49 +01:00
|
|
|
it('should load the config from a yaml file', async () => {
|
2024-10-01 16:03:55 -04:00
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.yaml' }));
|
2024-03-12 16:29:49 +01:00
|
|
|
const partialConfig = `
|
|
|
|
|
ffmpeg:
|
|
|
|
|
crf: 30
|
|
|
|
|
oauth:
|
|
|
|
|
autoLaunch: true
|
|
|
|
|
trash:
|
|
|
|
|
days: 10
|
|
|
|
|
user:
|
|
|
|
|
deleteDelay: 15
|
|
|
|
|
`;
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.readFile.mockResolvedValue(partialConfig);
|
2024-03-12 16:29:49 +01:00
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig);
|
2024-03-12 16:29:49 +01:00
|
|
|
|
2024-05-15 18:58:23 -04:00
|
|
|
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.yaml');
|
2024-03-12 16:29:49 +01:00
|
|
|
});
|
|
|
|
|
|
2023-08-25 19:44:52 +02:00
|
|
|
it('should accept an empty configuration file', async () => {
|
2024-10-01 16:03:55 -04:00
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify({}));
|
2023-08-25 19:44:52 +02:00
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
await expect(sut.getSystemConfig()).resolves.toEqual(defaults);
|
2023-08-25 19:44:52 +02:00
|
|
|
|
2024-05-15 18:58:23 -04:00
|
|
|
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
|
2023-08-25 19:44:52 +02:00
|
|
|
});
|
|
|
|
|
|
2023-10-17 17:34:16 -04:00
|
|
|
it('should allow underscores in the machine learning url', async () => {
|
2024-10-01 16:03:55 -04:00
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
2024-12-04 15:17:47 -05:00
|
|
|
const partialConfig = { machineLearning: { urls: ['immich_machine_learning'] } };
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify(partialConfig));
|
2023-10-17 17:34:16 -04:00
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
const config = await sut.getSystemConfig();
|
2024-12-04 15:17:47 -05:00
|
|
|
expect(config.machineLearning.urls).toEqual(['immich_machine_learning']);
|
2023-10-17 17:34:16 -04:00
|
|
|
});
|
|
|
|
|
|
2024-09-23 17:40:25 +02:00
|
|
|
const externalDomainTests = [
|
|
|
|
|
{ should: 'with a trailing slash', externalDomain: 'https://demo.immich.app/' },
|
|
|
|
|
{ should: 'without a trailing slash', externalDomain: 'https://demo.immich.app' },
|
|
|
|
|
{ should: 'with a port', externalDomain: 'https://demo.immich.app:42', result: 'https://demo.immich.app:42' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const { should, externalDomain, result } of externalDomainTests) {
|
|
|
|
|
it(`should normalize an external domain ${should}`, async () => {
|
2024-10-01 16:03:55 -04:00
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
2024-09-23 17:40:25 +02:00
|
|
|
const partialConfig = { server: { externalDomain } };
|
|
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify(partialConfig));
|
|
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
const config = await sut.getSystemConfig();
|
2024-09-23 17:40:25 +02:00
|
|
|
expect(config.server.externalDomain).toEqual(result ?? 'https://demo.immich.app');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 16:29:49 +01:00
|
|
|
it('should warn for unknown options in yaml', async () => {
|
2024-10-01 16:03:55 -04:00
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.yaml' }));
|
2024-03-12 16:29:49 +01:00
|
|
|
const partialConfig = `
|
|
|
|
|
unknownOption: true
|
|
|
|
|
`;
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.readFile.mockResolvedValue(partialConfig);
|
2024-03-12 16:29:49 +01:00
|
|
|
|
2024-09-30 17:31:21 -04:00
|
|
|
await sut.getSystemConfig();
|
2024-04-17 03:00:31 +05:30
|
|
|
expect(loggerMock.warn).toHaveBeenCalled();
|
2024-03-12 16:29:49 +01:00
|
|
|
});
|
|
|
|
|
|
2023-08-25 19:44:52 +02:00
|
|
|
const tests = [
|
|
|
|
|
{ should: 'validate numbers', config: { ffmpeg: { crf: 'not-a-number' } } },
|
|
|
|
|
{ should: 'validate booleans', config: { oauth: { enabled: 'invalid' } } },
|
|
|
|
|
{ should: 'validate enums', config: { ffmpeg: { transcode: 'unknown' } } },
|
|
|
|
|
{ should: 'validate required oauth fields', config: { oauth: { enabled: true } } },
|
2023-12-20 20:47:56 -05:00
|
|
|
{ should: 'warn for top level unknown options', warn: true, config: { unknownOption: true } },
|
|
|
|
|
{ should: 'warn for nested unknown options', warn: true, config: { ffmpeg: { unknownOption: true } } },
|
2023-08-25 19:44:52 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const test of tests) {
|
|
|
|
|
it(`should ${test.should}`, async () => {
|
2024-10-01 16:03:55 -04:00
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify(test.config));
|
2023-08-25 19:44:52 +02:00
|
|
|
|
2023-12-20 20:47:56 -05:00
|
|
|
if (test.warn) {
|
2024-09-30 17:31:21 -04:00
|
|
|
await sut.getSystemConfig();
|
2024-04-17 03:00:31 +05:30
|
|
|
expect(loggerMock.warn).toHaveBeenCalled();
|
2023-12-20 20:47:56 -05:00
|
|
|
} else {
|
2024-09-30 17:31:21 -04:00
|
|
|
await expect(sut.getSystemConfig()).rejects.toBeInstanceOf(Error);
|
2023-12-20 20:47:56 -05:00
|
|
|
}
|
2023-08-25 19:44:52 +02:00
|
|
|
});
|
|
|
|
|
}
|
2023-01-21 11:11:55 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('updateConfig', () => {
|
2024-09-30 10:35:11 -04:00
|
|
|
it('should update the config and emit an event', async () => {
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.get.mockResolvedValue(partialConfig);
|
2024-09-30 17:31:21 -04:00
|
|
|
await expect(sut.updateSystemConfig(updatedConfig)).resolves.toEqual(updatedConfig);
|
2024-09-30 10:35:11 -04:00
|
|
|
expect(eventMock.emit).toHaveBeenCalledWith(
|
|
|
|
|
'config.update',
|
|
|
|
|
expect.objectContaining({ oldConfig: expect.any(Object), newConfig: updatedConfig }),
|
|
|
|
|
);
|
2023-01-21 11:11:55 -05:00
|
|
|
});
|
|
|
|
|
|
2023-08-25 19:44:52 +02:00
|
|
|
it('should throw an error if a config file is in use', async () => {
|
2024-10-01 16:03:55 -04:00
|
|
|
configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
|
2024-05-15 18:58:23 -04:00
|
|
|
systemMock.readFile.mockResolvedValue(JSON.stringify({}));
|
2024-09-30 17:31:21 -04:00
|
|
|
await expect(sut.updateSystemConfig(defaults)).rejects.toBeInstanceOf(BadRequestException);
|
2024-05-15 18:58:23 -04:00
|
|
|
expect(systemMock.set).not.toHaveBeenCalled();
|
2023-08-25 19:44:52 +02:00
|
|
|
});
|
2023-01-21 11:11:55 -05:00
|
|
|
});
|
|
|
|
|
|
2023-11-17 23:13:36 -05:00
|
|
|
describe('getCustomCss', () => {
|
2023-10-25 15:13:05 -07:00
|
|
|
it('should return the default theme', async () => {
|
2023-11-17 23:13:36 -05:00
|
|
|
await expect(sut.getCustomCss()).resolves.toEqual(defaults.theme.customCss);
|
2023-10-25 15:13:05 -07:00
|
|
|
});
|
|
|
|
|
});
|
2023-01-21 11:11:55 -05:00
|
|
|
});
|