mirror of
https://github.com/immich-app/immich
synced 2026-08-22 13:13:05 +00:00
test: write cli service tests
This commit is contained in:
parent
9d869a88b6
commit
f57c8b2b69
4 changed files with 104 additions and 23 deletions
|
|
@ -1,11 +1,16 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { createAdapter } from '@socket.io/redis-adapter';
|
||||
import Redis from 'ioredis';
|
||||
import { Server as SocketIO } from 'socket.io';
|
||||
import { ExitCode } from 'src/enum';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { AppRestartEvent } from 'src/repositories/event.repository';
|
||||
|
||||
@Injectable()
|
||||
export class MaintenanceRepository {
|
||||
private closeFn?: () => Promise<void>;
|
||||
|
||||
constructor() {}
|
||||
constructor(private configRepository: ConfigRepository) {}
|
||||
|
||||
exitApp() {
|
||||
/* eslint-disable unicorn/no-process-exit */
|
||||
|
|
@ -19,4 +24,19 @@ export class MaintenanceRepository {
|
|||
setCloseFn(fn: () => Promise<void>) {
|
||||
this.closeFn = fn;
|
||||
}
|
||||
|
||||
sendOneShotAppRestart(state: AppRestartEvent): void {
|
||||
const server = new SocketIO();
|
||||
const pubClient = new Redis(this.configRepository.getEnv().redis);
|
||||
const subClient = pubClient.duplicate();
|
||||
server.adapter(createAdapter(pubClient, subClient));
|
||||
|
||||
// => corresponds to notification.service.ts#onAppRestart
|
||||
server.emit('AppRestartV1', state, () => {
|
||||
server.serverSideEmit('AppRestart', state, () => {
|
||||
pubClient.disconnect();
|
||||
subClient.disconnect();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { jwtVerify } from 'jose';
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { CliService } from 'src/services/cli.service';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { newTestService, ServiceMocks } from 'test/utils';
|
||||
|
|
@ -80,6 +82,84 @@ describe(CliService.name, () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('disableMaintenanceMode', () => {
|
||||
it('should not do anything if not in maintenance mode', async () => {
|
||||
mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: false });
|
||||
await expect(sut.disableMaintenanceMode()).resolves.toEqual({
|
||||
alreadyDisabled: true
|
||||
});
|
||||
|
||||
expect(mocks.systemMetadata.set).toHaveBeenCalledTimes(0);
|
||||
expect(mocks.event.emit).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should disable maintenance mode', async () => {
|
||||
mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: true, secret: 'secret' });
|
||||
await expect(sut.disableMaintenanceMode()).resolves.toEqual({
|
||||
alreadyDisabled: false
|
||||
});
|
||||
|
||||
expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.MaintenanceMode, {
|
||||
isMaintenanceMode: false,
|
||||
});
|
||||
|
||||
expect(mocks.maintenance.sendOneShotAppRestart).toHaveBeenCalledWith({
|
||||
isMaintenanceMode: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('enableMaintenanceMode', () => {
|
||||
it('should not do anything if in maintenance mode', async () => {
|
||||
mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: true, secret: 'secret' });
|
||||
await expect(sut.enableMaintenanceMode()).resolves.toEqual(expect.objectContaining({
|
||||
alreadyEnabled: true
|
||||
}));
|
||||
|
||||
expect(mocks.systemMetadata.set).toHaveBeenCalledTimes(0);
|
||||
expect(mocks.event.emit).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should enable maintenance mode', async () => {
|
||||
mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: false });
|
||||
await expect(sut.enableMaintenanceMode()).resolves.toEqual(expect.objectContaining({
|
||||
alreadyEnabled: false
|
||||
}));
|
||||
|
||||
expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.MaintenanceMode, {
|
||||
isMaintenanceMode: true,
|
||||
secret: expect.stringMatching(/^\w{128}$/),
|
||||
});
|
||||
|
||||
expect(mocks.maintenance.sendOneShotAppRestart).toHaveBeenCalledWith({
|
||||
isMaintenanceMode: true,
|
||||
});
|
||||
});
|
||||
|
||||
const RE_LOGIN_URL = /https:\/\/my.immich.app\/maintenance\?token=([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)/;
|
||||
|
||||
it('should return a valid login URL', async () => {
|
||||
mocks.systemMetadata.get.mockResolvedValue({ isMaintenanceMode: true, secret: 'secret' });
|
||||
|
||||
const result = await sut.enableMaintenanceMode();
|
||||
|
||||
expect(result).toEqual(expect.objectContaining({
|
||||
authUrl: expect.stringMatching(RE_LOGIN_URL),
|
||||
alreadyEnabled: true,
|
||||
}));
|
||||
|
||||
const token = RE_LOGIN_URL.exec(result.authUrl)![1];
|
||||
|
||||
await expect(jwtVerify(token, new TextEncoder().encode("secret"))).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
payload: expect.objectContaining({
|
||||
username: 'cli-admin',
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disableOAuthLogin', () => {
|
||||
it('should disable oauth login', async () => {
|
||||
await sut.disableOAuthLogin();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { createAdapter } from '@socket.io/redis-adapter';
|
||||
import Redis from 'ioredis';
|
||||
import { isAbsolute } from 'node:path';
|
||||
import { Server } from 'socket.io';
|
||||
import { SALT_ROUNDS } from 'src/constants';
|
||||
import { MaintenanceAuthDto } from 'src/dtos/maintenance.dto';
|
||||
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { AppRestartEvent } from 'src/repositories/event.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { MaintenanceService } from 'src/services/maintenance.service';
|
||||
import { getExternalDomain } from 'src/utils/misc';
|
||||
|
|
@ -46,21 +42,6 @@ export class CliService extends BaseService {
|
|||
await this.updateConfig(config);
|
||||
}
|
||||
|
||||
private sendOneShotAppRestart(state: AppRestartEvent): void {
|
||||
const server = new Server();
|
||||
const pubClient = new Redis(this.configRepository.getEnv().redis);
|
||||
const subClient = pubClient.duplicate();
|
||||
server.adapter(createAdapter(pubClient, subClient));
|
||||
|
||||
// => corresponds to notification.service.ts#onAppRestart
|
||||
server.emit('AppRestartV1', state, () => {
|
||||
server.serverSideEmit('AppRestart', state, () => {
|
||||
pubClient.disconnect();
|
||||
subClient.disconnect();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async disableMaintenanceMode(): Promise<{ alreadyDisabled: boolean }> {
|
||||
const currentState = await this.systemMetadataRepository
|
||||
.get(SystemMetadataKey.MaintenanceMode)
|
||||
|
|
@ -75,7 +56,7 @@ export class CliService extends BaseService {
|
|||
const state = { isMaintenanceMode: false as const };
|
||||
await this.systemMetadataRepository.set(SystemMetadataKey.MaintenanceMode, state);
|
||||
|
||||
this.sendOneShotAppRestart(state);
|
||||
this.maintenanceRepository.sendOneShotAppRestart(state);
|
||||
|
||||
return {
|
||||
alreadyDisabled: false,
|
||||
|
|
@ -108,7 +89,7 @@ export class CliService extends BaseService {
|
|||
secret,
|
||||
});
|
||||
|
||||
this.sendOneShotAppRestart({
|
||||
this.maintenanceRepository.sendOneShotAppRestart({
|
||||
isMaintenanceMode: true,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ export const getMocks = () => {
|
|||
apiKey: automock(ApiKeyRepository),
|
||||
library: automock(LibraryRepository, { strict: false }),
|
||||
machineLearning: automock(MachineLearningRepository, { args: [loggerMock], strict: false }),
|
||||
maintenance: automock(MaintenanceRepository),
|
||||
maintenance: automock(MaintenanceRepository, { strict: false }),
|
||||
map: automock(MapRepository, { args: [undefined, undefined, { setContext: () => {} }] }),
|
||||
media: newMediaRepositoryMock(),
|
||||
memory: automock(MemoryRepository),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue