mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix(database restores): don't assume onboarding has completed (#27052)
This commit is contained in:
@@ -27,6 +27,7 @@ describe(DatabaseBackupService.name, () => {
|
||||
mocks.systemMetadata as never,
|
||||
mocks.process,
|
||||
mocks.database as never,
|
||||
mocks.user as never,
|
||||
mocks.cron as never,
|
||||
mocks.job as never,
|
||||
maintenanceHealthRepositoryMock as never,
|
||||
@@ -187,6 +188,7 @@ describe(DatabaseBackupService.name, () => {
|
||||
mocks.systemMetadata as never,
|
||||
mocks.process,
|
||||
mocks.database as never,
|
||||
mocks.user as never,
|
||||
mocks.cron as never,
|
||||
mocks.job as never,
|
||||
void 0 as never,
|
||||
@@ -400,6 +402,7 @@ describe(DatabaseBackupService.name, () => {
|
||||
mocks.systemMetadata as never,
|
||||
mocks.process,
|
||||
mocks.database as never,
|
||||
mocks.user as never,
|
||||
mocks.cron as never,
|
||||
mocks.job as never,
|
||||
void 0 as never,
|
||||
@@ -474,6 +477,7 @@ describe(DatabaseBackupService.name, () => {
|
||||
mocks.systemMetadata as never,
|
||||
mocks.process,
|
||||
mocks.database as never,
|
||||
mocks.user as never,
|
||||
mocks.cron as never,
|
||||
mocks.job as never,
|
||||
void 0 as never,
|
||||
@@ -536,6 +540,7 @@ describe(DatabaseBackupService.name, () => {
|
||||
mocks.systemMetadata as never,
|
||||
mocks.process,
|
||||
mocks.database as never,
|
||||
mocks.user as never,
|
||||
mocks.cron as never,
|
||||
mocks.job as never,
|
||||
void 0 as never,
|
||||
@@ -663,6 +668,7 @@ describe(DatabaseBackupService.name, () => {
|
||||
mocks.systemMetadata as never,
|
||||
mocks.process,
|
||||
mocks.database as never,
|
||||
mocks.user as never,
|
||||
mocks.cron as never,
|
||||
mocks.job as never,
|
||||
maintenanceHealthRepositoryMock,
|
||||
@@ -678,6 +684,8 @@ describe(DatabaseBackupService.name, () => {
|
||||
it('should successfully restore a backup', async () => {
|
||||
let writtenToPsql = '';
|
||||
|
||||
mocks.user.hasAdmin.mockResolvedValue(true);
|
||||
|
||||
mocks.process.spawnDuplexStream.mockImplementationOnce(() => mockDuplex()('command', 0, 'data', ''));
|
||||
mocks.process.spawnDuplexStream.mockImplementationOnce(() => mockDuplex()('command', 0, 'data', ''));
|
||||
mocks.process.spawnDuplexStream.mockImplementationOnce(() => {
|
||||
@@ -740,6 +748,8 @@ describe(DatabaseBackupService.name, () => {
|
||||
it('should generate pg_dumpall specific SQL instructions', async () => {
|
||||
let writtenToPsql = '';
|
||||
|
||||
mocks.user.hasAdmin.mockResolvedValue(true);
|
||||
|
||||
mocks.process.spawnDuplexStream.mockImplementationOnce(() => mockDuplex()('command', 0, 'data', ''));
|
||||
mocks.process.spawnDuplexStream.mockImplementationOnce(() => mockDuplex()('command', 0, 'data', ''));
|
||||
mocks.process.spawnDuplexStream.mockImplementationOnce(() => {
|
||||
@@ -834,7 +844,24 @@ describe(DatabaseBackupService.name, () => {
|
||||
expect(mocks.process.spawnDuplexStream).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('should rollback if there is no admin user', async () => {
|
||||
mocks.user.hasAdmin.mockResolvedValue(false);
|
||||
|
||||
const progress = vitest.fn();
|
||||
await expect(
|
||||
sut.restoreDatabaseBackup('development-filename.sql', progress),
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Server health check failed, no admin exists.]`);
|
||||
|
||||
expect(progress).toHaveBeenCalledWith('backup', 0.05);
|
||||
expect(progress).toHaveBeenCalledWith('migrations', 0.9);
|
||||
expect(progress).toHaveBeenCalledWith('rollback', 0);
|
||||
|
||||
expect(mocks.user.hasAdmin).toHaveBeenCalled();
|
||||
expect(mocks.process.spawnDuplexStream).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('should rollback if API healthcheck fails', async () => {
|
||||
mocks.user.hasAdmin.mockResolvedValue(true);
|
||||
maintenanceHealthRepositoryMock.checkApiHealth.mockRejectedValue(new Error('Health Error'));
|
||||
|
||||
const progress = vitest.fn();
|
||||
@@ -846,6 +873,7 @@ describe(DatabaseBackupService.name, () => {
|
||||
expect(progress).toHaveBeenCalledWith('migrations', 0.9);
|
||||
expect(progress).toHaveBeenCalledWith('rollback', 0);
|
||||
|
||||
expect(mocks.user.hasAdmin).toHaveBeenCalled();
|
||||
expect(maintenanceHealthRepositoryMock.checkApiHealth).toHaveBeenCalled();
|
||||
expect(mocks.process.spawnDuplexStream).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { ProcessRepository } from 'src/repositories/process.repository';
|
||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { UserRepository } from 'src/repositories/user.repository';
|
||||
import { getConfig } from 'src/utils/config';
|
||||
import {
|
||||
findDatabaseBackupVersion,
|
||||
@@ -40,6 +41,7 @@ export class DatabaseBackupService {
|
||||
private readonly systemMetadataRepository: SystemMetadataRepository,
|
||||
private readonly processRepository: ProcessRepository,
|
||||
private readonly databaseRepository: DatabaseRepository,
|
||||
private readonly userRepository: UserRepository,
|
||||
@Optional()
|
||||
private readonly cronRepository: CronRepository,
|
||||
@Optional()
|
||||
@@ -405,7 +407,14 @@ export class DatabaseBackupService {
|
||||
|
||||
try {
|
||||
progressCb?.('migrations', 0.9);
|
||||
|
||||
await this.databaseRepository.runMigrations();
|
||||
|
||||
const hasAdmin = await this.userRepository.hasAdmin();
|
||||
if (!hasAdmin) {
|
||||
throw new Error('Server health check failed, no admin exists.');
|
||||
}
|
||||
|
||||
await this.maintenanceHealthRepository.checkApiHealth();
|
||||
} catch (error) {
|
||||
progressCb?.('rollback', 0);
|
||||
|
||||
Reference in New Issue
Block a user