mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Merge branch 'main' into feat/mobile-ocr
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Selectable } from 'kysely';
|
||||
import { AssetOrder } from 'src/enum';
|
||||
import { AlbumUserRole, AssetOrder } from 'src/enum';
|
||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||
import { AlbumUserFactory } from 'test/factories/album-user.factory';
|
||||
@@ -10,15 +10,12 @@ import { UserFactory } from 'test/factories/user.factory';
|
||||
import { newDate, newUuid, newUuidV7 } from 'test/small.factory';
|
||||
|
||||
export class AlbumFactory {
|
||||
#owner: UserFactory;
|
||||
#owner!: UserFactory;
|
||||
#sharedLinks: Selectable<SharedLinkTable>[] = [];
|
||||
#albumUsers: AlbumUserFactory[] = [];
|
||||
#assets: AssetFactory[] = [];
|
||||
|
||||
private constructor(private readonly value: Selectable<AlbumTable>) {
|
||||
value.ownerId ??= newUuid();
|
||||
this.#owner = UserFactory.from({ id: value.ownerId });
|
||||
}
|
||||
private constructor(private readonly value: Selectable<AlbumTable>) {}
|
||||
|
||||
static create(dto: AlbumLike = {}) {
|
||||
return AlbumFactory.from(dto).build();
|
||||
@@ -27,7 +24,6 @@ export class AlbumFactory {
|
||||
static from(dto: AlbumLike = {}) {
|
||||
return new AlbumFactory({
|
||||
id: newUuid(),
|
||||
ownerId: newUuid(),
|
||||
albumName: 'My Album',
|
||||
albumThumbnailAssetId: null,
|
||||
createdAt: newDate(),
|
||||
@@ -43,7 +39,7 @@ export class AlbumFactory {
|
||||
|
||||
owner(dto: UserLike = {}, builder?: FactoryBuilder<UserFactory>) {
|
||||
this.#owner = build(UserFactory.from(dto), builder);
|
||||
this.value.ownerId = this.#owner.build().id;
|
||||
this.albumUser({ userId: this.#owner.build().id, role: AlbumUserRole.Owner });
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -53,7 +49,7 @@ export class AlbumFactory {
|
||||
}
|
||||
|
||||
albumUser(dto: AlbumUserLike = {}, builder?: FactoryBuilder<AlbumUserFactory>) {
|
||||
const albumUser = build(AlbumUserFactory.from(dto).album(this.value), builder);
|
||||
const albumUser = build(AlbumUserFactory.from(dto), builder);
|
||||
this.#albumUsers.push(albumUser);
|
||||
return this;
|
||||
}
|
||||
@@ -78,7 +74,6 @@ export class AlbumFactory {
|
||||
build() {
|
||||
return {
|
||||
...this.value,
|
||||
owner: this.#owner.build(),
|
||||
assets: this.#assets.map((asset) => asset.build()),
|
||||
albumUsers: this.#albumUsers.map((albumUser) => albumUser.build()),
|
||||
sharedLinks: this.#sharedLinks,
|
||||
|
||||
@@ -54,8 +54,6 @@ export class AssetFactory {
|
||||
status: AssetStatus.Active,
|
||||
checksum: newSha1(),
|
||||
checksumAlgorithm: ChecksumAlgorithm.sha1File,
|
||||
deviceAssetId: '',
|
||||
deviceId: '',
|
||||
duplicateId: null,
|
||||
duration: null,
|
||||
fileCreatedAt: new Date(now++),
|
||||
|
||||
@@ -25,6 +25,7 @@ export class SessionFactory {
|
||||
updateId: newUuidV7(),
|
||||
updatedAt: newDate(),
|
||||
userId: newUuid(),
|
||||
oauthSid: newUuid(),
|
||||
...dto,
|
||||
});
|
||||
}
|
||||
|
||||
-52
@@ -1,5 +1,4 @@
|
||||
import { MapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { SharedLinkResponseDto } from 'src/dtos/shared-link.dto';
|
||||
import { SharedLinkType } from 'src/enum';
|
||||
import { AssetFactory } from 'test/factories/asset.factory';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
@@ -70,23 +69,6 @@ export const sharedLinkStub = {
|
||||
album: null,
|
||||
slug: null,
|
||||
}),
|
||||
readonlyNoExif: Object.freeze({
|
||||
id: '123',
|
||||
userId: authStub.admin.user.id,
|
||||
key: sharedLinkBytes,
|
||||
type: SharedLinkType.Individual,
|
||||
createdAt: today,
|
||||
expiresAt: tomorrow,
|
||||
allowUpload: false,
|
||||
allowDownload: false,
|
||||
showExif: false,
|
||||
description: null,
|
||||
password: null,
|
||||
assets: [],
|
||||
albumId: null,
|
||||
album: null,
|
||||
slug: null,
|
||||
}),
|
||||
passwordRequired: Object.freeze({
|
||||
id: '123',
|
||||
userId: authStub.admin.user.id,
|
||||
@@ -105,37 +87,3 @@ export const sharedLinkStub = {
|
||||
album: null,
|
||||
}),
|
||||
};
|
||||
|
||||
export const sharedLinkResponseStub = {
|
||||
valid: Object.freeze<SharedLinkResponseDto>({
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
assets: [],
|
||||
createdAt: today,
|
||||
description: null,
|
||||
password: null,
|
||||
expiresAt: tomorrow,
|
||||
id: '123',
|
||||
key: sharedLinkBytes.toString('base64url'),
|
||||
showMetadata: true,
|
||||
type: SharedLinkType.Album,
|
||||
userId: 'admin_id',
|
||||
slug: null,
|
||||
}),
|
||||
expired: Object.freeze<SharedLinkResponseDto>({
|
||||
album: undefined,
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
assets: [],
|
||||
createdAt: today,
|
||||
description: null,
|
||||
password: null,
|
||||
expiresAt: yesterday,
|
||||
id: '123',
|
||||
key: sharedLinkBytes.toString('base64url'),
|
||||
showMetadata: true,
|
||||
type: SharedLinkType.Album,
|
||||
userId: 'admin_id',
|
||||
slug: null,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -84,7 +84,6 @@ export const getForAlbum = (album: ReturnType<AlbumFactory['build']>) => ({
|
||||
createdAt: albumUser.createdAt.toISOString(),
|
||||
user: getDehydrated(albumUser.user),
|
||||
})),
|
||||
owner: getDehydrated(album.owner),
|
||||
sharedLinks: album.sharedLinks.map((sharedLink) => getDehydrated(sharedLink)),
|
||||
});
|
||||
|
||||
@@ -127,8 +126,6 @@ export const getForMetadataExtraction = (asset: ReturnType<AssetFactory['build']
|
||||
id: asset.id,
|
||||
checksum: asset.checksum,
|
||||
checksumAlgorithm: asset.checksumAlgorithm,
|
||||
deviceAssetId: asset.deviceAssetId,
|
||||
deviceId: asset.deviceId,
|
||||
fileCreatedAt: asset.fileCreatedAt,
|
||||
fileModifiedAt: asset.fileModifiedAt,
|
||||
isExternal: asset.isExternal,
|
||||
@@ -221,7 +218,6 @@ export const getForSharedLink = (sharedLink: ReturnType<SharedLinkFactory['build
|
||||
album: sharedLink.album
|
||||
? {
|
||||
...getDehydrated(sharedLink.album),
|
||||
owner: getDehydrated(sharedLink.album.owner),
|
||||
assets: sharedLink.album.assets.map((asset) => getDehydrated(asset)),
|
||||
}
|
||||
: null,
|
||||
|
||||
@@ -222,9 +222,14 @@ export class MediumTestContext<S extends BaseService = BaseService> {
|
||||
return { result };
|
||||
}
|
||||
|
||||
async newAlbum(dto: Insertable<AlbumTable>, assetIds?: string[]) {
|
||||
async newAlbum({ ownerId, ...dto }: Insertable<AlbumTable> & { ownerId: string }, assetIds?: string[]) {
|
||||
const album = mediumFactory.albumInsert(dto);
|
||||
const result = await this.get(AlbumRepository).create(album, assetIds ?? [], []);
|
||||
const result = await this.get(AlbumRepository).create(
|
||||
album,
|
||||
assetIds ?? [],
|
||||
[{ userId: ownerId, role: AlbumUserRole.Owner }],
|
||||
ownerId,
|
||||
);
|
||||
return { album, result };
|
||||
}
|
||||
|
||||
@@ -549,8 +554,6 @@ const assetInsert = (asset: Partial<Insertable<AssetTable>> = {}) => {
|
||||
const id = asset.id || newUuid();
|
||||
const now = newDate();
|
||||
const defaults: Insertable<AssetTable> = {
|
||||
deviceAssetId: '',
|
||||
deviceId: '',
|
||||
originalFileName: '',
|
||||
checksum: randomBytes(32),
|
||||
checksumAlgorithm: ChecksumAlgorithm.sha1File,
|
||||
@@ -572,9 +575,9 @@ const assetInsert = (asset: Partial<Insertable<AssetTable>> = {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
const albumInsert = (album: Partial<Insertable<AlbumTable>> & { ownerId: string }) => {
|
||||
const albumInsert = (album: Partial<Insertable<AlbumTable>>) => {
|
||||
const id = album.id || newUuid();
|
||||
const defaults: Omit<Insertable<AlbumTable>, 'ownerId'> = {
|
||||
const defaults: Insertable<AlbumTable> = {
|
||||
albumName: 'Album',
|
||||
};
|
||||
|
||||
|
||||
@@ -67,48 +67,3 @@ export const errorDto = {
|
||||
correlationId: expect.any(String),
|
||||
},
|
||||
};
|
||||
|
||||
export const signupResponseDto = {
|
||||
admin: {
|
||||
avatarColor: expect.any(String),
|
||||
id: expect.any(String),
|
||||
name: 'Immich Admin',
|
||||
email: 'admin@immich.cloud',
|
||||
storageLabel: 'admin',
|
||||
profileImagePath: '',
|
||||
// why? lol
|
||||
shouldChangePassword: true,
|
||||
isAdmin: true,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
deletedAt: null,
|
||||
oauthId: '',
|
||||
quotaUsageInBytes: 0,
|
||||
quotaSizeInBytes: null,
|
||||
status: 'active',
|
||||
license: null,
|
||||
profileChangedAt: expect.any(String),
|
||||
},
|
||||
};
|
||||
|
||||
export const loginResponseDto = {
|
||||
admin: {
|
||||
accessToken: expect.any(String),
|
||||
name: 'Immich Admin',
|
||||
isAdmin: true,
|
||||
profileImagePath: '',
|
||||
shouldChangePassword: true,
|
||||
userEmail: 'admin@immich.cloud',
|
||||
userId: expect.any(String),
|
||||
},
|
||||
};
|
||||
export const deviceDto = {
|
||||
current: {
|
||||
id: expect.any(String),
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
current: true,
|
||||
deviceOS: '',
|
||||
deviceType: '',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -52,8 +52,6 @@ describe(AssetService.name, () => {
|
||||
sut.uploadAsset(
|
||||
auth,
|
||||
{
|
||||
deviceId: 'some-id',
|
||||
deviceAssetId: 'some-id',
|
||||
fileModifiedAt: new Date(),
|
||||
fileCreatedAt: new Date(),
|
||||
assetData: Buffer.from('some data'),
|
||||
@@ -66,7 +64,7 @@ describe(AssetService.name, () => {
|
||||
});
|
||||
|
||||
expect(ctx.getMock(EventRepository).emit).toHaveBeenCalledWith('AssetCreate', {
|
||||
asset: expect.objectContaining({ deviceAssetId: 'some-id' }),
|
||||
asset: expect.objectContaining({}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,8 +85,6 @@ describe(AssetService.name, () => {
|
||||
sut.uploadAsset(
|
||||
auth,
|
||||
{
|
||||
deviceId: 'some-id',
|
||||
deviceAssetId: 'some-id',
|
||||
fileModifiedAt: new Date(),
|
||||
fileCreatedAt: new Date(),
|
||||
assetData: Buffer.from('some data'),
|
||||
@@ -125,8 +121,6 @@ describe(AssetService.name, () => {
|
||||
const auth = factory.auth({ user: { id: user.id }, sharedLink });
|
||||
const file = mediumFactory.uploadFile();
|
||||
const uploadDto = {
|
||||
deviceId: 'some-id',
|
||||
deviceAssetId: 'some-id',
|
||||
fileModifiedAt: new Date(),
|
||||
fileCreatedAt: new Date(),
|
||||
assetData: Buffer.from('some data'),
|
||||
@@ -166,8 +160,6 @@ describe(AssetService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id }, sharedLink });
|
||||
const uploadDto = {
|
||||
deviceId: 'some-id',
|
||||
deviceAssetId: 'some-id',
|
||||
fileModifiedAt: new Date(),
|
||||
fileCreatedAt: new Date(),
|
||||
assetData: Buffer.from('some data'),
|
||||
@@ -206,8 +198,6 @@ describe(AssetService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id }, sharedLink });
|
||||
const uploadDto = {
|
||||
deviceId: 'some-id',
|
||||
deviceAssetId: 'some-id',
|
||||
fileModifiedAt: new Date(),
|
||||
fileCreatedAt: new Date(),
|
||||
assetData: Buffer.from('some data'),
|
||||
@@ -248,8 +238,6 @@ describe(AssetService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id }, sharedLink });
|
||||
const uploadDto = {
|
||||
deviceId: 'some-id',
|
||||
deviceAssetId: 'some-id',
|
||||
fileModifiedAt: new Date(),
|
||||
fileCreatedAt: new Date(),
|
||||
assetData: Buffer.from('some data'),
|
||||
|
||||
@@ -25,6 +25,14 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
||||
const { albumUser } = await ctx.newAlbumUser({ albumId: album.id, userId: user.id, role: AlbumUserRole.Editor });
|
||||
|
||||
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1])).resolves.toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
albumId: album.id,
|
||||
role: AlbumUserRole.Owner,
|
||||
}),
|
||||
type: SyncEntityType.AlbumUserV1,
|
||||
},
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
@@ -47,6 +55,14 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||
expect(response).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
albumId: album.id,
|
||||
role: AlbumUserRole.Owner,
|
||||
}),
|
||||
type: SyncEntityType.AlbumUserV1,
|
||||
},
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
@@ -136,6 +152,14 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||
expect(response).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
albumId: album.id,
|
||||
role: AlbumUserRole.Owner,
|
||||
}),
|
||||
type: SyncEntityType.AlbumUserV1,
|
||||
},
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
@@ -163,6 +187,7 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||
expect(response).toEqual([
|
||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||
@@ -201,6 +226,7 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||
expect(response).toEqual([
|
||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||
expect.objectContaining({ type: SyncEntityType.AlbumUserV1 }),
|
||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||
@@ -229,10 +255,11 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
||||
|
||||
it('should backfill album users when a user shares an album with you', async () => {
|
||||
const { auth, ctx } = await setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: user1 } = await ctx.newUser();
|
||||
const { user: user2 } = await ctx.newUser();
|
||||
const { album: album1 } = await ctx.newAlbum({ ownerId: user1.id });
|
||||
const { album: album2 } = await ctx.newAlbum({ ownerId: user1.id });
|
||||
const { album: album1 } = await ctx.newAlbum({ ownerId: user.id });
|
||||
const { album: album2 } = await ctx.newAlbum({ ownerId: user.id });
|
||||
// backfill album user
|
||||
await ctx.newAlbumUser({ albumId: album1.id, userId: user1.id, role: AlbumUserRole.Editor });
|
||||
await wait(2);
|
||||
@@ -244,6 +271,15 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
||||
|
||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||
expect(response).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
albumId: album2.id,
|
||||
role: AlbumUserRole.Owner,
|
||||
userId: user.id,
|
||||
}),
|
||||
type: SyncEntityType.AlbumUserV1,
|
||||
},
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
@@ -264,6 +300,15 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
||||
// should backfill the album user
|
||||
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumUsersV1]);
|
||||
expect(newResponse).toEqual([
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
albumId: album1.id,
|
||||
role: AlbumUserRole.Owner,
|
||||
userId: user.id,
|
||||
}),
|
||||
type: SyncEntityType.AlbumUserBackfillV1,
|
||||
},
|
||||
{
|
||||
ack: expect.any(String),
|
||||
data: expect.objectContaining({
|
||||
|
||||
@@ -30,7 +30,6 @@ describe(SyncRequestType.AlbumsV1, () => {
|
||||
data: expect.objectContaining({
|
||||
id: album.id,
|
||||
name: album.albumName,
|
||||
ownerId: album.ownerId,
|
||||
}),
|
||||
type: SyncEntityType.AlbumV1,
|
||||
},
|
||||
|
||||
@@ -15,13 +15,10 @@ export const newAssetRepositoryMock = (): Mocked<RepositoryInterface<AssetReposi
|
||||
getByDayOfYear: vitest.fn(),
|
||||
getByIds: vitest.fn().mockResolvedValue([]),
|
||||
getByIdsWithAllRelationsButStacks: vitest.fn().mockResolvedValue([]),
|
||||
getByDeviceIds: vitest.fn(),
|
||||
getById: vitest.fn(),
|
||||
getByChecksum: vitest.fn(),
|
||||
getByChecksums: vitest.fn(),
|
||||
getUploadAssetIdByChecksum: vitest.fn(),
|
||||
getRandom: vitest.fn(),
|
||||
getAllByDeviceId: vitest.fn(),
|
||||
getLivePhotoCount: vitest.fn(),
|
||||
getLibraryAssetCount: vitest.fn(),
|
||||
updateAll: vitest.fn(),
|
||||
@@ -34,8 +31,6 @@ export const newAssetRepositoryMock = (): Mocked<RepositoryInterface<AssetReposi
|
||||
getTimeBucket: vitest.fn(),
|
||||
getTimeBuckets: vitest.fn(),
|
||||
getAssetIdByCity: vitest.fn(),
|
||||
getAllForUserFullSync: vitest.fn(),
|
||||
getChangedDeltaSync: vitest.fn(),
|
||||
upsertFile: vitest.fn(),
|
||||
upsertFiles: vitest.fn(),
|
||||
deleteFile: vitest.fn(),
|
||||
|
||||
+13
-49
@@ -1,12 +1,13 @@
|
||||
import { createPostgres, DatabaseConnectionParams } from '@immich/sql-tools';
|
||||
import { CallHandler, ExecutionContext, Provider, ValidationPipe } from '@nestjs/common';
|
||||
import { APP_GUARD, APP_PIPE } from '@nestjs/core';
|
||||
import { CallHandler, ExecutionContext, Provider } from '@nestjs/common';
|
||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
|
||||
import { transformException } from '@nestjs/platform-express/multer/multer/multer.utils';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { ClassConstructor } from 'class-transformer';
|
||||
import { NextFunction } from 'express';
|
||||
import { Kysely } from 'kysely';
|
||||
import multer from 'multer';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { ZodSerializerInterceptor, ZodValidationPipe } from 'nestjs-zod';
|
||||
import { ChildProcessWithoutNullStreams } from 'node:child_process';
|
||||
import { Duplex, Readable, Writable } from 'node:stream';
|
||||
import { PNG } from 'pngjs';
|
||||
@@ -14,6 +15,7 @@ import { UploadFieldName } from 'src/dtos/asset-media.dto';
|
||||
import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor';
|
||||
import { AuthGuard } from 'src/middleware/auth.guard';
|
||||
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
|
||||
import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||
@@ -23,7 +25,6 @@ import { AppRepository } from 'src/repositories/app.repository';
|
||||
import { AssetEditRepository } from 'src/repositories/asset-edit.repository';
|
||||
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { AuditRepository } from 'src/repositories/audit.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { CronRepository } from 'src/repositories/cron.repository';
|
||||
import { CryptoRepository } from 'src/repositories/crypto.repository';
|
||||
@@ -90,7 +91,7 @@ export type ControllerContext = {
|
||||
close: () => Promise<void>;
|
||||
};
|
||||
|
||||
export const controllerSetup = async (controller: ClassConstructor<unknown>, providers: Provider[]) => {
|
||||
export const controllerSetup = async (controller: new (...args: any[]) => unknown, providers: Provider[]) => {
|
||||
const noopInterceptor = { intercept: (ctx: never, next: CallHandler<unknown>) => next.handle() };
|
||||
const upload = multer({ storage: multer.memoryStorage() });
|
||||
const memoryFileInterceptor = {
|
||||
@@ -113,9 +114,12 @@ export const controllerSetup = async (controller: ClassConstructor<unknown>, pro
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
controllers: [controller],
|
||||
providers: [
|
||||
{ provide: APP_PIPE, useValue: new ValidationPipe({ transform: true, whitelist: true }) },
|
||||
{ provide: APP_FILTER, useClass: GlobalExceptionFilter },
|
||||
{ provide: APP_PIPE, useClass: ZodValidationPipe },
|
||||
{ provide: APP_INTERCEPTOR, useClass: ZodSerializerInterceptor },
|
||||
{ provide: APP_GUARD, useClass: AuthGuard },
|
||||
{ provide: LoggingRepository, useValue: LoggingRepository.create() },
|
||||
{ provide: ClsService, useValue: { getId: vi.fn() } },
|
||||
{ provide: AuthService, useValue: { authenticate: vi.fn() } },
|
||||
...providers,
|
||||
],
|
||||
@@ -158,14 +162,14 @@ const mockFn = (label: string, { strict }: { strict: boolean }) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const mockBaseService = <T extends BaseService>(service: ClassConstructor<T>) => {
|
||||
export const mockBaseService = <T extends BaseService>(service: new (...args: any[]) => T) => {
|
||||
return automock(service, { args: [{ setContext: () => {} }], strict: false });
|
||||
};
|
||||
|
||||
export const automock = <T>(
|
||||
Dependency: ClassConstructor<T>,
|
||||
Dependency: new (...args: any[]) => T,
|
||||
options?: {
|
||||
args?: ConstructorParameters<ClassConstructor<T>>;
|
||||
args?: ConstructorParameters<new (...args: any[]) => T>;
|
||||
strict?: boolean;
|
||||
},
|
||||
): AutoMocked<T> => {
|
||||
@@ -214,7 +218,6 @@ export type ServiceOverrides = {
|
||||
albumUser: AlbumUserRepository;
|
||||
apiKey: ApiKeyRepository;
|
||||
app: AppRepository;
|
||||
audit: AuditRepository;
|
||||
asset: AssetRepository;
|
||||
assetEdit: AssetEditRepository;
|
||||
assetJob: AssetJobRepository;
|
||||
@@ -294,7 +297,6 @@ export const getMocks = () => {
|
||||
cron: automock(CronRepository, { args: [, loggerMock] }),
|
||||
crypto: newCryptoRepositoryMock(),
|
||||
activity: automock(ActivityRepository),
|
||||
audit: automock(AuditRepository),
|
||||
album: automock(AlbumRepository, { strict: false }),
|
||||
albumUser: automock(AlbumUserRepository),
|
||||
asset: newAssetRepositoryMock(),
|
||||
@@ -368,7 +370,6 @@ export const newTestService = <T extends BaseService>(
|
||||
overrides.asset || (mocks.asset as As<AssetRepository>),
|
||||
overrides.assetEdit || (mocks.assetEdit as As<AssetEditRepository>),
|
||||
overrides.assetJob || (mocks.assetJob as As<AssetJobRepository>),
|
||||
overrides.audit || (mocks.audit as As<AuditRepository>),
|
||||
overrides.config || (mocks.config as As<ConfigRepository> as ConfigRepository),
|
||||
overrides.cron || (mocks.cron as As<CronRepository>),
|
||||
overrides.crypto || (mocks.crypto as As<CryptoRepository>),
|
||||
@@ -530,43 +531,6 @@ export const mockDuplex =
|
||||
return duplex;
|
||||
};
|
||||
|
||||
export const mockFork = vitest.fn((exitCode: number, stdout: string, stderr: string, error?: unknown) => {
|
||||
const stdoutStream = new Readable({
|
||||
read() {
|
||||
this.push(stdout); // write mock data to stdout
|
||||
this.push(null); // end stream
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
stdout: stdoutStream,
|
||||
stderr: new Readable({
|
||||
read() {
|
||||
this.push(stderr); // write mock data to stderr
|
||||
this.push(null); // end stream
|
||||
},
|
||||
}),
|
||||
stdin: new Writable({
|
||||
write(chunk, encoding, callback) {
|
||||
callback();
|
||||
},
|
||||
}),
|
||||
exitCode,
|
||||
on: vitest.fn((event, callback: any) => {
|
||||
if (event === 'close') {
|
||||
stdoutStream.once('end', () => callback(0));
|
||||
}
|
||||
if (event === 'error' && error) {
|
||||
stdoutStream.once('end', () => callback(error));
|
||||
}
|
||||
if (event === 'exit') {
|
||||
stdoutStream.once('end', () => callback(exitCode));
|
||||
}
|
||||
}),
|
||||
kill: vitest.fn(),
|
||||
} as unknown as ChildProcessWithoutNullStreams;
|
||||
});
|
||||
|
||||
export async function* makeStream<T>(items: T[] = []): AsyncIterableIterator<T> {
|
||||
for (const item of items) {
|
||||
await Promise.resolve();
|
||||
|
||||
Reference in New Issue
Block a user