fix(web): avoid formatting a missing user deletion date

This commit is contained in:
Codex
2026-10-17 20:09:47 +03:00
parent 43b94defd9
commit 97609ec6bd
2 changed files with 56 additions and 3 deletions
@@ -0,0 +1,53 @@
import { UserStatus, type UserAdminResponseDto } from '@immich/sdk';
import type { MessageFormatter } from 'svelte-i18n';
import { getUserAdminActions } from '$lib/services/user-admin.service';
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
vi.mock('@immich/ui', () => ({ modalManager: { show: vi.fn(), showDialog: vi.fn() } }));
vi.mock('$lib/managers/auth-manager.svelte', () => ({ authManager: { user: { id: 'self' } } }));
vi.mock('$lib/managers/server-config-manager.svelte', () => ({
serverConfigManager: { value: { userDeleteDelay: 7 } },
}));
vi.mock('$lib/modals/PasswordResetSuccessModal.svelte', () => ({ default: {} }));
vi.mock('$lib/modals/UserDeleteConfirmModal.svelte', () => ({ default: {} }));
vi.mock('$lib/modals/UserRestoreConfirmModal.svelte', () => ({ default: {} }));
vi.mock('$lib/queries/client', () => ({ executeMutation: vi.fn() }));
vi.mock('$lib/queries/user-admin.query', () => ({ updateUserAdminMutation: vi.fn() }));
vi.mock('$lib/route', () => ({ Route: { newUser: vi.fn(), viewUser: vi.fn(), editUser: vi.fn() } }));
vi.mock('$lib/utils/i18n', () => ({ getFormatter: vi.fn() }));
describe('admin actions for active and deleted accounts', () => {
it('does not format a deletion date for an active account', () => {
const format = vi.fn((key: string) => key);
const user = { id: 'other', deletedAt: null } as UserAdminResponseDto;
const actions = getUserAdminActions(format as MessageFormatter, user);
expect(actions.Restore.data).toBeUndefined();
expect((actions.Restore.$if as () => boolean)()).toBe(false);
expect(format).not.toHaveBeenCalledWith('admin.user_restore_scheduled_removal', expect.anything());
expect((actions.Update.$if as () => boolean)()).toBe(true);
expect((actions.ResetPassword.$if as () => boolean)()).toBe(true);
});
it('offers restoration with a valid date and hides edits for a deleted account', () => {
const format = vi.fn((key: string, _options?: unknown) => key);
const user = { id: 'other', deletedAt: '2026-10-17T12:00:00Z', status: UserStatus.Deleted } as UserAdminResponseDto;
const actions = getUserAdminActions(format as MessageFormatter, user);
expect((actions.Restore.$if as () => boolean)()).toBe(true);
expect(format).toHaveBeenCalledWith('admin.user_restore_scheduled_removal', {
values: { date: new Date('2026-10-24T12:00:00Z') },
});
for (const action of [actions.Update, actions.Delete, actions.ResetPassword, actions.ResetPinCode]) {
expect((action.$if as () => boolean)()).toBe(false);
}
});
it('keeps destructive account actions hidden for the current administrator', () => {
const format = vi.fn((key: string) => key);
const actions = getUserAdminActions(
format as MessageFormatter,
{ id: 'self', deletedAt: null } as UserAdminResponseDto,
);
expect((actions.Delete.$if as () => boolean)()).toBe(false);
expect((actions.ResetPassword.$if as () => boolean)()).toBe(false);
});
});
+3 -3
View File
@@ -65,9 +65,9 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons
icon: mdiDeleteRestore, icon: mdiDeleteRestore,
title: $t('restore'), title: $t('restore'),
color: 'primary', color: 'primary',
data: { data: user.deletedAt
title: $t('admin.user_restore_scheduled_removal', { values: { date: getDeleteDate(user.deletedAt!) } }), ? { title: $t('admin.user_restore_scheduled_removal', { values: { date: getDeleteDate(user.deletedAt) } }) }
}, : undefined,
$if: () => !!user.deletedAt && user.status === UserStatus.Deleted, $if: () => !!user.deletedAt && user.status === UserStatus.Deleted,
onAction: () => modalManager.show(UserRestoreConfirmModal, { user }), onAction: () => modalManager.show(UserRestoreConfirmModal, { user }),
}; };