mirror of
https://github.com/immich-app/immich.git
synced 2026-02-15 05:18:37 +03:00
Merge branch 'lighter_buckets_web' into lighter_buckets_server
This commit is contained in:
12
server/test/fixtures/user.stub.ts
vendored
12
server/test/fixtures/user.stub.ts
vendored
@@ -1,5 +1,5 @@
|
||||
import { UserAdmin } from 'src/database';
|
||||
import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum';
|
||||
import { UserStatus } from 'src/enum';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
|
||||
export const userStub = {
|
||||
@@ -12,6 +12,7 @@ export const userStub = {
|
||||
storageLabel: 'admin',
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
avatarColor: null,
|
||||
profileImagePath: '',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
@@ -28,16 +29,12 @@ export const userStub = {
|
||||
storageLabel: null,
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
avatarColor: null,
|
||||
profileImagePath: '',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
metadata: [
|
||||
{
|
||||
key: UserMetadataKey.PREFERENCES,
|
||||
value: { avatar: { color: UserAvatarColor.PRIMARY } },
|
||||
},
|
||||
],
|
||||
metadata: [],
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
},
|
||||
@@ -50,6 +47,7 @@ export const userStub = {
|
||||
storageLabel: null,
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
avatarColor: null,
|
||||
profileImagePath: '',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
|
||||
@@ -13,9 +13,11 @@ import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { CryptoRepository } from 'src/repositories/crypto.repository';
|
||||
import { DatabaseRepository } from 'src/repositories/database.repository';
|
||||
import { EmailRepository } from 'src/repositories/email.repository';
|
||||
import { JobRepository } from 'src/repositories/job.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||
import { NotificationRepository } from 'src/repositories/notification.repository';
|
||||
import { PartnerRepository } from 'src/repositories/partner.repository';
|
||||
import { PersonRepository } from 'src/repositories/person.repository';
|
||||
import { SearchRepository } from 'src/repositories/search.repository';
|
||||
@@ -42,10 +44,12 @@ type RepositoriesTypes = {
|
||||
config: ConfigRepository;
|
||||
crypto: CryptoRepository;
|
||||
database: DatabaseRepository;
|
||||
email: EmailRepository;
|
||||
job: JobRepository;
|
||||
user: UserRepository;
|
||||
logger: LoggingRepository;
|
||||
memory: MemoryRepository;
|
||||
notification: NotificationRepository;
|
||||
partner: PartnerRepository;
|
||||
person: PersonRepository;
|
||||
search: SearchRepository;
|
||||
@@ -142,6 +146,11 @@ export const getRepository = <K extends keyof RepositoriesTypes>(key: K, db: Kys
|
||||
return new DatabaseRepository(db, new LoggingRepository(undefined, configRepo), configRepo);
|
||||
}
|
||||
|
||||
case 'email': {
|
||||
const logger = new LoggingRepository(undefined, new ConfigRepository());
|
||||
return new EmailRepository(logger);
|
||||
}
|
||||
|
||||
case 'logger': {
|
||||
const configMock = { getEnv: () => ({ noColor: false }) };
|
||||
return new LoggingRepository(undefined, configMock as ConfigRepository);
|
||||
@@ -151,6 +160,10 @@ export const getRepository = <K extends keyof RepositoriesTypes>(key: K, db: Kys
|
||||
return new MemoryRepository(db);
|
||||
}
|
||||
|
||||
case 'notification': {
|
||||
return new NotificationRepository(db);
|
||||
}
|
||||
|
||||
case 'partner': {
|
||||
return new PartnerRepository(db);
|
||||
}
|
||||
@@ -221,6 +234,10 @@ const getRepositoryMock = <K extends keyof RepositoryMocks>(key: K) => {
|
||||
});
|
||||
}
|
||||
|
||||
case 'email': {
|
||||
return automock(EmailRepository, { args: [{ setContext: () => {} }] });
|
||||
}
|
||||
|
||||
case 'job': {
|
||||
return automock(JobRepository, { args: [undefined, undefined, undefined, { setContext: () => {} }] });
|
||||
}
|
||||
@@ -234,6 +251,10 @@ const getRepositoryMock = <K extends keyof RepositoryMocks>(key: K) => {
|
||||
return automock(MemoryRepository);
|
||||
}
|
||||
|
||||
case 'notification': {
|
||||
return automock(NotificationRepository);
|
||||
}
|
||||
|
||||
case 'partner': {
|
||||
return automock(PartnerRepository);
|
||||
}
|
||||
@@ -284,7 +305,7 @@ export const asDeps = (repositories: ServiceOverrides) => {
|
||||
repositories.crypto || getRepositoryMock('crypto'),
|
||||
repositories.database || getRepositoryMock('database'),
|
||||
repositories.downloadRepository,
|
||||
repositories.email,
|
||||
repositories.email || getRepositoryMock('email'),
|
||||
repositories.event,
|
||||
repositories.job || getRepositoryMock('job'),
|
||||
repositories.library,
|
||||
@@ -294,6 +315,7 @@ export const asDeps = (repositories: ServiceOverrides) => {
|
||||
repositories.memory || getRepositoryMock('memory'),
|
||||
repositories.metadata,
|
||||
repositories.move,
|
||||
repositories.notification || getRepositoryMock('notification'),
|
||||
repositories.oauth,
|
||||
repositories.partner || getRepositoryMock('partner'),
|
||||
repositories.person || getRepositoryMock('person'),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Kysely } from 'kysely';
|
||||
import { parse } from 'pg-connection-string';
|
||||
import { DB } from 'src/db';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { DatabaseRepository } from 'src/repositories/database.repository';
|
||||
@@ -37,19 +36,10 @@ const globalSetup = async () => {
|
||||
|
||||
const postgresPort = postgresContainer.getMappedPort(5432);
|
||||
const postgresUrl = `postgres://postgres:postgres@localhost:${postgresPort}/immich`;
|
||||
const parsed = parse(postgresUrl);
|
||||
|
||||
process.env.IMMICH_TEST_POSTGRES_URL = postgresUrl;
|
||||
|
||||
const db = new Kysely<DB>(
|
||||
getKyselyConfig({
|
||||
...parsed,
|
||||
ssl: false,
|
||||
host: parsed.host ?? undefined,
|
||||
port: parsed.port ? Number(parsed.port) : undefined,
|
||||
database: parsed.database ?? undefined,
|
||||
}),
|
||||
);
|
||||
const db = new Kysely<DB>(getKyselyConfig({ connectionType: 'url', url: postgresUrl }));
|
||||
|
||||
const configRepository = new ConfigRepository();
|
||||
const logger = new LoggingRepository(undefined, configRepository);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { NotificationController } from 'src/controllers/notification.controller';
|
||||
import { AuthService } from 'src/services/auth.service';
|
||||
import { NotificationService } from 'src/services/notification.service';
|
||||
import request from 'supertest';
|
||||
import { errorDto } from 'test/medium/responses';
|
||||
import { createControllerTestApp, TestControllerApp } from 'test/medium/utils';
|
||||
import { factory } from 'test/small.factory';
|
||||
|
||||
describe(NotificationController.name, () => {
|
||||
let realApp: TestControllerApp;
|
||||
let mockApp: TestControllerApp;
|
||||
|
||||
beforeEach(async () => {
|
||||
realApp = await createControllerTestApp({ authType: 'real' });
|
||||
mockApp = await createControllerTestApp({ authType: 'mock' });
|
||||
});
|
||||
|
||||
describe('GET /notifications', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(realApp.getHttpServer()).get('/notifications');
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
|
||||
it('should call the service with an auth dto', async () => {
|
||||
const auth = factory.auth({ user: factory.user() });
|
||||
mockApp.getMockedService(AuthService).authenticate.mockResolvedValue(auth);
|
||||
const service = mockApp.getMockedService(NotificationService);
|
||||
|
||||
const { status } = await request(mockApp.getHttpServer())
|
||||
.get('/notifications')
|
||||
.set('Authorization', `Bearer token`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(service.search).toHaveBeenCalledWith(auth, {});
|
||||
});
|
||||
|
||||
it(`should reject an invalid notification level`, async () => {
|
||||
const auth = factory.auth({ user: factory.user() });
|
||||
mockApp.getMockedService(AuthService).authenticate.mockResolvedValue(auth);
|
||||
const service = mockApp.getMockedService(NotificationService);
|
||||
|
||||
const { status, body } = await request(mockApp.getHttpServer())
|
||||
.get(`/notifications`)
|
||||
.query({ level: 'invalid' })
|
||||
.set('Authorization', `Bearer token`);
|
||||
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest([expect.stringContaining('level must be one of the following values')]));
|
||||
expect(service.search).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /notifications', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(realApp.getHttpServer())
|
||||
.put(`/notifications`)
|
||||
.send({ ids: [], readAt: new Date().toISOString() });
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /notifications/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(realApp.getHttpServer()).get(`/notifications/${factory.uuid()}`);
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /notifications/:id', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(realApp.getHttpServer())
|
||||
.put(`/notifications/${factory.uuid()}`)
|
||||
.send({ readAt: factory.date() });
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await realApp.close();
|
||||
await mockApp.close();
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,10 @@ export const newAccessRepositoryMock = (): IAccessRepositoryMock => {
|
||||
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
|
||||
},
|
||||
|
||||
notification: {
|
||||
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
|
||||
},
|
||||
|
||||
person: {
|
||||
checkFaceOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
|
||||
checkOwnerAccess: vitest.fn().mockResolvedValue(new Set()),
|
||||
|
||||
@@ -21,19 +21,12 @@ const envData: EnvData = {
|
||||
|
||||
database: {
|
||||
config: {
|
||||
kysely: { database: 'immich', host: 'database', port: 5432 },
|
||||
typeorm: {
|
||||
connectionType: 'parts',
|
||||
database: 'immich',
|
||||
type: 'postgres',
|
||||
host: 'database',
|
||||
port: 5432,
|
||||
username: 'postgres',
|
||||
password: 'postgres',
|
||||
name: 'immich',
|
||||
synchronize: false,
|
||||
migrationsRun: true,
|
||||
},
|
||||
connectionType: 'parts',
|
||||
database: 'immich',
|
||||
host: 'database',
|
||||
port: 5432,
|
||||
username: 'postgres',
|
||||
password: 'postgres',
|
||||
},
|
||||
|
||||
skipMigrations: false,
|
||||
|
||||
@@ -8,7 +8,7 @@ export const newMediaRepositoryMock = (): Mocked<RepositoryInterface<MediaReposi
|
||||
writeExif: vitest.fn().mockImplementation(() => Promise.resolve()),
|
||||
generateThumbhash: vitest.fn().mockResolvedValue(Buffer.from('')),
|
||||
decodeImage: vitest.fn().mockResolvedValue({ data: Buffer.from(''), info: {} }),
|
||||
extract: vitest.fn().mockResolvedValue(false),
|
||||
extract: vitest.fn().mockResolvedValue(null),
|
||||
probe: vitest.fn(),
|
||||
transcode: vitest.fn(),
|
||||
getImageDimensions: vitest.fn(),
|
||||
|
||||
@@ -140,6 +140,7 @@ const userFactory = (user: Partial<User> = {}) => ({
|
||||
id: newUuid(),
|
||||
name: 'Test User',
|
||||
email: 'test@immich.cloud',
|
||||
avatarColor: null,
|
||||
profileImagePath: '',
|
||||
profileChangedAt: newDate(),
|
||||
...user,
|
||||
@@ -155,6 +156,7 @@ const userAdminFactory = (user: Partial<UserAdmin> = {}) => {
|
||||
storageLabel = null,
|
||||
shouldChangePassword = false,
|
||||
isAdmin = false,
|
||||
avatarColor = null,
|
||||
createdAt = newDate(),
|
||||
updatedAt = newDate(),
|
||||
deletedAt = null,
|
||||
@@ -173,6 +175,7 @@ const userAdminFactory = (user: Partial<UserAdmin> = {}) => {
|
||||
storageLabel,
|
||||
shouldChangePassword,
|
||||
isAdmin,
|
||||
avatarColor,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
deletedAt,
|
||||
@@ -311,4 +314,5 @@ export const factory = {
|
||||
sidecarWrite: assetSidecarWriteFactory,
|
||||
},
|
||||
uuid: newUuid,
|
||||
date: newDate,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ClassConstructor } from 'class-transformer';
|
||||
import { Kysely, sql } from 'kysely';
|
||||
import { Kysely } from 'kysely';
|
||||
import { ChildProcessWithoutNullStreams } from 'node:child_process';
|
||||
import { Writable } from 'node:stream';
|
||||
import { parse } from 'pg-connection-string';
|
||||
import { PNG } from 'pngjs';
|
||||
import postgres from 'postgres';
|
||||
import { DB } from 'src/db';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
@@ -29,6 +29,7 @@ import { MediaRepository } from 'src/repositories/media.repository';
|
||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||
import { MetadataRepository } from 'src/repositories/metadata.repository';
|
||||
import { MoveRepository } from 'src/repositories/move.repository';
|
||||
import { NotificationRepository } from 'src/repositories/notification.repository';
|
||||
import { OAuthRepository } from 'src/repositories/oauth.repository';
|
||||
import { PartnerRepository } from 'src/repositories/partner.repository';
|
||||
import { PersonRepository } from 'src/repositories/person.repository';
|
||||
@@ -49,7 +50,7 @@ import { VersionHistoryRepository } from 'src/repositories/version-history.repos
|
||||
import { ViewRepository } from 'src/repositories/view-repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { RepositoryInterface } from 'src/types';
|
||||
import { getKyselyConfig } from 'src/utils/database';
|
||||
import { asPostgresConnectionConfig, getKyselyConfig } from 'src/utils/database';
|
||||
import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||
import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock';
|
||||
import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock';
|
||||
@@ -135,6 +136,7 @@ export type ServiceOverrides = {
|
||||
memory: MemoryRepository;
|
||||
metadata: MetadataRepository;
|
||||
move: MoveRepository;
|
||||
notification: NotificationRepository;
|
||||
oauth: OAuthRepository;
|
||||
partner: PartnerRepository;
|
||||
person: PersonRepository;
|
||||
@@ -202,6 +204,7 @@ export const newTestService = <T extends BaseService>(
|
||||
memory: automock(MemoryRepository),
|
||||
metadata: newMetadataRepositoryMock(),
|
||||
move: automock(MoveRepository, { strict: false }),
|
||||
notification: automock(NotificationRepository),
|
||||
oauth: automock(OAuthRepository, { args: [loggerMock] }),
|
||||
partner: automock(PartnerRepository, { strict: false }),
|
||||
person: newPersonRepositoryMock(),
|
||||
@@ -250,6 +253,7 @@ export const newTestService = <T extends BaseService>(
|
||||
overrides.memory || (mocks.memory as As<MemoryRepository>),
|
||||
overrides.metadata || (mocks.metadata as As<MetadataRepository>),
|
||||
overrides.move || (mocks.move as As<MoveRepository>),
|
||||
overrides.notification || (mocks.notification as As<NotificationRepository>),
|
||||
overrides.oauth || (mocks.oauth as As<OAuthRepository>),
|
||||
overrides.partner || (mocks.partner as As<PartnerRepository>),
|
||||
overrides.person || (mocks.person as As<PersonRepository>),
|
||||
@@ -297,24 +301,20 @@ function* newPngFactory() {
|
||||
|
||||
const pngFactory = newPngFactory();
|
||||
|
||||
const withDatabase = (url: string, name: string) => url.replace('/immich', `/${name}`);
|
||||
|
||||
export const getKyselyDB = async (suffix?: string): Promise<Kysely<DB>> => {
|
||||
const parsed = parse(process.env.IMMICH_TEST_POSTGRES_URL!);
|
||||
const testUrl = process.env.IMMICH_TEST_POSTGRES_URL!;
|
||||
const sql = postgres({
|
||||
...asPostgresConnectionConfig({ connectionType: 'url', url: withDatabase(testUrl, 'postgres') }),
|
||||
max: 1,
|
||||
});
|
||||
|
||||
const parsedOptions = {
|
||||
...parsed,
|
||||
ssl: false,
|
||||
host: parsed.host ?? undefined,
|
||||
port: parsed.port ? Number(parsed.port) : undefined,
|
||||
database: parsed.database ?? undefined,
|
||||
};
|
||||
|
||||
const kysely = new Kysely<DB>(getKyselyConfig({ ...parsedOptions, max: 1, database: 'postgres' }));
|
||||
const randomSuffix = Math.random().toString(36).slice(2, 7);
|
||||
const dbName = `immich_${suffix ?? randomSuffix}`;
|
||||
await sql.unsafe(`CREATE DATABASE ${dbName} WITH TEMPLATE immich OWNER postgres;`);
|
||||
|
||||
await sql.raw(`CREATE DATABASE ${dbName} WITH TEMPLATE immich OWNER postgres;`).execute(kysely);
|
||||
|
||||
return new Kysely<DB>(getKyselyConfig({ ...parsedOptions, database: dbName }));
|
||||
return new Kysely<DB>(getKyselyConfig({ connectionType: 'url', url: withDatabase(testUrl, dbName) }));
|
||||
};
|
||||
|
||||
export const newRandomImage = () => {
|
||||
|
||||
Reference in New Issue
Block a user