test: cover user admin queries and config compatibility for local.4

This commit is contained in:
Codex
2026-10-17 19:37:37 +03:00
parent 36a6e033aa
commit 85820ca45e
3 changed files with 151 additions and 1 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none'
version: 3.3.3-local.3+3030402
version: 3.3.3-local.4+3030403
environment:
sdk: '>=3.12.0 <4.0.0'
@@ -6,6 +6,51 @@ import 'package:openapi/api.dart';
void main() {
group('Test OpenApi Patching', () {
test('older public config deserializes with map and video defaults', () {
final config = PublicConfigDto.fromJson({
'oauth': {'autoLaunch': false, 'buttonText': 'Login', 'enabled': false},
'passwordLogin': {'enabled': true},
'server': {'loginPageMessage': ''},
});
expect(config, isNotNull);
expect(config!.map.enabled, isTrue);
expect(config.map.lightStyle, 'https://tiles.immich.cloud/v1/style/light.json');
expect(config.ffmpeg.realtime.enabled, isFalse);
});
test('public config keeps explicit server map and video settings', () {
final config = PublicConfigDto.fromJson({
'oauth': {'autoLaunch': false, 'buttonText': 'Login', 'enabled': false},
'passwordLogin': {'enabled': true},
'server': {'loginPageMessage': ''},
'map': {'enabled': false, 'lightStyle': 'https://example.com/light', 'darkStyle': 'https://example.com/dark'},
'ffmpeg': {
'realtime': {'enabled': true},
},
});
expect(config!.map.enabled, isFalse);
expect(config.map.lightStyle, 'https://example.com/light');
expect(config.ffmpeg.realtime.enabled, isTrue);
});
test('older user config defaults SMTP to disabled', () {
final value = <String, dynamic>{};
upgradeDto(value, 'UserConfigDto');
final notifications = UserConfigNotificationsDto.fromJson(value['notifications']);
expect(notifications!.smtp.enabled, isFalse);
});
test('user config preserves an enabled SMTP setting', () {
final value = <String, dynamic>{
'notifications': {
'smtp': {'enabled': true},
},
};
upgradeDto(value, 'UserConfigDto');
final notifications = UserConfigNotificationsDto.fromJson(value['notifications']);
expect(notifications!.smtp.enabled, isTrue);
});
test('upgradeDto', () {
dynamic value;
String targetType;
@@ -0,0 +1,105 @@
import {
createUserAdmin,
deleteUserAdmin,
restoreUserAdmin,
searchUsersAdmin,
updateUserAdmin,
type UserAdminResponseDto,
} from '@immich/sdk';
import { QueryObserver } from '@tanstack/svelte-query';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { executeMutation, queryClient } from '$lib/queries/client';
import {
createUserAdminMutation,
deleteUserAdminMutation,
getUserAdminQuery,
restoreUserAdminMutation,
searchUsersAdminQuery,
updateUserAdminMutation,
} from '$lib/queries/user-admin.query';
import { handleError } from '$lib/utils/handle-error';
vi.mock('$lib', () => ({ getHeatmapRange: vi.fn() }));
vi.mock('@immich/ui', () => ({ toastManager: { primary: vi.fn(), info: vi.fn() } }));
vi.mock('$lib/utils/handle-error', () => ({ handleError: vi.fn() }));
vi.mock('$lib/utils/i18n', () => ({ getFormatter: () => Promise.resolve((key: string) => key) }));
vi.mock('@immich/sdk', () => ({
CalendarHeatmapType: { Upload: 'upload' },
createUserAdmin: vi.fn(),
deleteUserAdmin: vi.fn(),
restoreUserAdmin: vi.fn(),
updateUserAdmin: vi.fn(),
searchUsersAdmin: vi.fn(),
getUserCalendarHeatmapAdmin: vi.fn(),
getUserPreferencesAdmin: vi.fn(),
getUserSessionsAdmin: vi.fn(),
getUserStatisticsAdmin: vi.fn(),
}));
const active = { id: 'active', name: 'Active', deletedAt: null } as UserAdminResponseDto;
const deleted = { id: 'deleted', name: 'Deleted', deletedAt: '2026-10-16T00:00:00Z' } as UserAdminResponseDto;
describe('admin user query regressions', () => {
beforeEach(() => {
queryClient.clear();
vi.resetAllMocks();
});
afterEach(() => queryClient.clear());
it('shares one request while filtering deleted users only for active-user consumers', async () => {
vi.mocked(searchUsersAdmin).mockResolvedValue([active, deleted]);
await Promise.all([queryClient.query(searchUsersAdminQuery()), queryClient.query(getUserAdminQuery(deleted.id))]);
const activeUsers = new QueryObserver(queryClient, searchUsersAdminQuery());
const allUsers = new QueryObserver(queryClient, searchUsersAdminQuery({ withDeleted: true }));
const detail = new QueryObserver(queryClient, getUserAdminQuery(deleted.id));
expect(searchUsersAdmin).toHaveBeenCalledTimes(1);
expect(activeUsers.getCurrentResult().data).toEqual([active]);
expect(allUsers.getCurrentResult().data).toEqual([active, deleted]);
expect(detail.getCurrentResult().data).toEqual(deleted);
activeUsers.destroy();
allUsers.destroy();
detail.destroy();
});
it('preserves the user list and reports an error when deletion fails', async () => {
queryClient.setQueryData(searchUsersAdminQuery().queryKey, [active, deleted]);
vi.mocked(deleteUserAdmin).mockRejectedValue(new Error('offline'));
await executeMutation(deleteUserAdminMutation, [{ id: active.id }]);
expect(queryClient.getQueryData(searchUsersAdminQuery().queryKey)).toEqual([active, deleted]);
expect(handleError).toHaveBeenCalledOnce();
});
it('uses the server response for edits without changing another user', async () => {
queryClient.setQueryData(searchUsersAdminQuery().queryKey, [active, deleted]);
const saved = { ...active, name: 'Saved', quotaSizeInBytes: 1024 };
vi.mocked(updateUserAdmin).mockResolvedValue(saved);
await executeMutation(updateUserAdminMutation, [{ id: active.id, userAdminUpdateDto: { name: saved.name } }]);
expect(queryClient.getQueryData(searchUsersAdminQuery().queryKey)).toEqual([saved, deleted]);
});
it('makes a restored account available to active-user consumers', async () => {
queryClient.setQueryData(searchUsersAdminQuery().queryKey, [active, deleted]);
const restored = { ...deleted, deletedAt: null };
vi.mocked(restoreUserAdmin).mockResolvedValue(restored);
await executeMutation(restoreUserAdminMutation, [{ id: deleted.id }]);
const observer = new QueryObserver(queryClient, searchUsersAdminQuery());
expect(observer.getCurrentResult().data).toEqual([active, restored]);
observer.destroy();
});
it('refreshes the user list after account creation', async () => {
queryClient.setQueryData(searchUsersAdminQuery().queryKey, [active]);
vi.mocked(createUserAdmin).mockResolvedValue(deleted);
await executeMutation(createUserAdminMutation, [
{ userAdminCreateDto: { email: 'test@example.com', name: 'Test', password: 'test-only-password' } },
]);
expect(queryClient.getQueryState(searchUsersAdminQuery().queryKey)?.isInvalidated).toBe(true);
});
it('clears admin user data on logout', () => {
queryClient.setQueryData(searchUsersAdminQuery().queryKey, [active, deleted]);
queryClient.setQueryData(['user-admin', active.id, 'sessions'], [{ id: 'session' }]);
eventManager.emit('AuthLogout');
expect(queryClient.getQueryCache().getAll()).toHaveLength(0);
});
});