refactor: better types for getList and getDeletedAfter (#16926)

This commit is contained in:
Jason Rasmussen
2025-03-17 15:32:12 -04:00
committed by GitHub
parent 93907a89d8
commit 6a40aa83b7
13 changed files with 342 additions and 194 deletions
+3
View File
@@ -29,6 +29,7 @@ import { SharedLinkRepository } from 'src/repositories/shared-link.repository';
import { StackRepository } from 'src/repositories/stack.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
import { SyncRepository } from 'src/repositories/sync.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { TelemetryRepository } from 'src/repositories/telemetry.repository';
import { TrashRepository } from 'src/repositories/trash.repository';
import { UserRepository } from 'src/repositories/user.repository';
@@ -205,6 +206,7 @@ export class TestContext {
sharedLink: SharedLinkRepository;
stack: StackRepository;
storage: StorageRepository;
systemMetadata: SystemMetadataRepository;
sync: SyncRepository;
telemetry: TelemetryRepository;
trash: TrashRepository;
@@ -241,6 +243,7 @@ export class TestContext {
this.stack = new StackRepository(this.db);
this.storage = new StorageRepository(logger);
this.sync = new SyncRepository(this.db);
this.systemMetadata = new SystemMetadataRepository(this.db);
this.telemetry = newTelemetryRepositoryMock() as unknown as TelemetryRepository;
this.trash = new TrashRepository(this.db);
this.user = new UserRepository(this.db);
-5
View File
@@ -47,11 +47,6 @@ export const systemConfigStub = {
defaultStorageQuota: 1,
},
},
deleteDelay30: {
user: {
deleteDelay: 30,
},
},
libraryWatchEnabled: {
library: {
scan: {
+60 -4
View File
@@ -1,15 +1,25 @@
import { Kysely } from 'kysely';
import { DateTime } from 'luxon';
import { DB } from 'src/db';
import { JobName, JobStatus } from 'src/enum';
import { UserService } from 'src/services/user.service';
import { TestContext, TestFactory } from 'test/factory';
import { getKyselyDB, newTestService } from 'test/utils';
import { getKyselyDB, newTestService, ServiceMocks } from 'test/utils';
const setup = async (db: Kysely<DB>) => {
const context = await TestContext.from(db).withUser({ isAdmin: true }).create();
const { sut, mocks } = newTestService(UserService, context);
return { sut, mocks, context };
};
describe.concurrent(UserService.name, () => {
let sut: UserService;
let context: TestContext;
let mocks: ServiceMocks;
beforeAll(async () => {
const db = await getKyselyDB();
context = await TestContext.from(db).withUser({ isAdmin: true }).create();
({ sut } = newTestService(UserService, context));
({ sut, context, mocks } = await setup(await getKyselyDB()));
});
describe('create', () => {
@@ -113,4 +123,50 @@ describe.concurrent(UserService.name, () => {
expect(getResponse).toEqual(after);
});
});
describe('handleUserDeleteCheck', () => {
it('should work when there are no deleted users', async () => {
await expect(sut.handleUserDeleteCheck()).resolves.toEqual(JobStatus.SUCCESS);
expect(mocks.job.queueAll).toHaveBeenCalledWith([]);
});
it('should work when there is a user to delete', async () => {
const { sut, context, mocks } = await setup(await getKyselyDB());
const user = TestFactory.user({ deletedAt: DateTime.now().minus({ days: 25 }).toJSDate() });
await context.createUser(user);
await expect(sut.handleUserDeleteCheck()).resolves.toEqual(JobStatus.SUCCESS);
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.USER_DELETION, data: { id: user.id } }]);
});
it('should skip a recently deleted user', async () => {
const { sut, context, mocks } = await setup(await getKyselyDB());
const user = TestFactory.user({ deletedAt: DateTime.now().minus({ days: 5 }).toJSDate() });
await context.createUser(user);
await expect(sut.handleUserDeleteCheck()).resolves.toEqual(JobStatus.SUCCESS);
expect(mocks.job.queueAll).toHaveBeenCalledWith([]);
});
it('should respect a custom user delete delay', async () => {
const db = await getKyselyDB();
const { sut, context, mocks } = await setup(db);
const user = TestFactory.user({ deletedAt: DateTime.now().minus({ days: 25 }).toJSDate() });
await context.createUser(user);
const config = await sut.getConfig({ withCache: false });
config.user.deleteDelay = 30;
await sut.updateConfig(config);
await expect(sut.handleUserDeleteCheck()).resolves.toEqual(JobStatus.SUCCESS);
expect(mocks.job.queueAll).toHaveBeenCalledWith([]);
});
});
});
+23 -2
View File
@@ -1,8 +1,8 @@
import { randomUUID } from 'node:crypto';
import { ApiKey, Asset, AuthApiKey, AuthUser, Library, Partner, User } from 'src/database';
import { ApiKey, Asset, AuthApiKey, AuthUser, Library, Partner, User, UserAdmin } from 'src/database';
import { AuthDto } from 'src/dtos/auth.dto';
import { OnThisDayData } from 'src/entities/memory.entity';
import { AssetStatus, AssetType, MemoryType, Permission } from 'src/enum';
import { AssetStatus, AssetType, MemoryType, Permission, UserStatus } from 'src/enum';
import { ActivityItem, MemoryItem } from 'src/types';
export const newUuid = () => randomUUID() as string;
@@ -85,6 +85,26 @@ const userFactory = (user: Partial<User> = {}) => ({
...user,
});
const userAdminFactory = (user: Partial<UserAdmin> = {}) => ({
id: newUuid(),
name: 'Test User',
email: 'test@immich.cloud',
profileImagePath: '',
profileChangedAt: newDate(),
storageLabel: null,
shouldChangePassword: false,
isAdmin: false,
createdAt: newDate(),
updatedAt: newDate(),
deletedAt: null,
oauthId: '',
quotaSizeInBytes: null,
quotaUsageInBytes: 0,
status: UserStatus.ACTIVE,
metadata: [],
...user,
});
const assetFactory = (asset: Partial<Asset> = {}) => ({
id: newUuid(),
createdAt: newDate(),
@@ -198,5 +218,6 @@ export const factory = {
session: sessionFactory,
stack: stackFactory,
user: userFactory,
userAdmin: userAdminFactory,
versionHistory: versionHistoryFactory,
};