mirror of
https://github.com/immich-app/immich.git
synced 2025-06-03 05:34:32 -04:00
refactor: convert activity stub to a factory (#16702)
This commit is contained in:
parent
f82786a297
commit
2d106755f6
@ -15,6 +15,14 @@ export type AuthApiKey = {
|
|||||||
permissions: Permission[];
|
permissions: Permission[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
profileImagePath: string;
|
||||||
|
profileChangedAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
export type AuthSharedLink = {
|
export type AuthSharedLink = {
|
||||||
id: string;
|
id: string;
|
||||||
expiresAt: Date | null;
|
expiresAt: Date | null;
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import { ReactionType } from 'src/dtos/activity.dto';
|
import { ReactionType } from 'src/dtos/activity.dto';
|
||||||
import { ActivityService } from 'src/services/activity.service';
|
import { ActivityService } from 'src/services/activity.service';
|
||||||
import { activityStub } from 'test/fixtures/activity.stub';
|
import { factory, newUuid, newUuids } from 'test/small.factory';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
|
||||||
import { newTestService, ServiceMocks } from 'test/utils';
|
import { newTestService, ServiceMocks } from 'test/utils';
|
||||||
|
|
||||||
describe(ActivityService.name, () => {
|
describe(ActivityService.name, () => {
|
||||||
@ -19,137 +18,118 @@ describe(ActivityService.name, () => {
|
|||||||
|
|
||||||
describe('getAll', () => {
|
describe('getAll', () => {
|
||||||
it('should get all', async () => {
|
it('should get all', async () => {
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
|
const [albumId, assetId, userId] = newUuids();
|
||||||
|
|
||||||
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
|
||||||
mocks.activity.search.mockResolvedValue([]);
|
mocks.activity.search.mockResolvedValue([]);
|
||||||
|
|
||||||
await expect(sut.getAll(authStub.admin, { assetId: 'asset-id', albumId: 'album-id' })).resolves.toEqual([]);
|
await expect(sut.getAll(factory.auth({ id: userId }), { assetId, albumId })).resolves.toEqual([]);
|
||||||
|
|
||||||
expect(mocks.activity.search).toHaveBeenCalledWith({
|
expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: undefined });
|
||||||
assetId: 'asset-id',
|
|
||||||
albumId: 'album-id',
|
|
||||||
isLiked: undefined,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter by type=like', async () => {
|
it('should filter by type=like', async () => {
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
|
const [albumId, assetId, userId] = newUuids();
|
||||||
|
|
||||||
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
|
||||||
mocks.activity.search.mockResolvedValue([]);
|
mocks.activity.search.mockResolvedValue([]);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.getAll(authStub.admin, { assetId: 'asset-id', albumId: 'album-id', type: ReactionType.LIKE }),
|
sut.getAll(factory.auth({ id: userId }), { assetId, albumId, type: ReactionType.LIKE }),
|
||||||
).resolves.toEqual([]);
|
).resolves.toEqual([]);
|
||||||
|
|
||||||
expect(mocks.activity.search).toHaveBeenCalledWith({
|
expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: true });
|
||||||
assetId: 'asset-id',
|
|
||||||
albumId: 'album-id',
|
|
||||||
isLiked: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter by type=comment', async () => {
|
it('should filter by type=comment', async () => {
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
|
const [albumId, assetId] = newUuids();
|
||||||
|
|
||||||
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
|
||||||
mocks.activity.search.mockResolvedValue([]);
|
mocks.activity.search.mockResolvedValue([]);
|
||||||
|
|
||||||
await expect(
|
await expect(sut.getAll(factory.auth(), { assetId, albumId, type: ReactionType.COMMENT })).resolves.toEqual([]);
|
||||||
sut.getAll(authStub.admin, { assetId: 'asset-id', albumId: 'album-id', type: ReactionType.COMMENT }),
|
|
||||||
).resolves.toEqual([]);
|
|
||||||
|
|
||||||
expect(mocks.activity.search).toHaveBeenCalledWith({
|
expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: false });
|
||||||
assetId: 'asset-id',
|
|
||||||
albumId: 'album-id',
|
|
||||||
isLiked: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getStatistics', () => {
|
describe('getStatistics', () => {
|
||||||
it('should get the comment count', async () => {
|
it('should get the comment count', async () => {
|
||||||
|
const [albumId, assetId] = newUuids();
|
||||||
|
|
||||||
mocks.activity.getStatistics.mockResolvedValue(1);
|
mocks.activity.getStatistics.mockResolvedValue(1);
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([activityStub.oneComment.albumId]));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
|
||||||
await expect(
|
|
||||||
sut.getStatistics(authStub.admin, {
|
await expect(sut.getStatistics(factory.auth(), { assetId, albumId })).resolves.toEqual({ comments: 1 });
|
||||||
assetId: 'asset-id',
|
|
||||||
albumId: activityStub.oneComment.albumId,
|
|
||||||
}),
|
|
||||||
).resolves.toEqual({ comments: 1 });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('addComment', () => {
|
describe('addComment', () => {
|
||||||
it('should require access to the album', async () => {
|
it('should require access to the album', async () => {
|
||||||
|
const [albumId, assetId] = newUuids();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.create(authStub.admin, {
|
sut.create(factory.auth(), { albumId, assetId, type: ReactionType.COMMENT, comment: 'comment' }),
|
||||||
albumId: 'album-id',
|
|
||||||
assetId: 'asset-id',
|
|
||||||
type: ReactionType.COMMENT,
|
|
||||||
comment: 'comment',
|
|
||||||
}),
|
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a comment', async () => {
|
it('should create a comment', async () => {
|
||||||
mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set(['album-id']));
|
const [albumId, assetId, userId] = newUuids();
|
||||||
mocks.activity.create.mockResolvedValue(activityStub.oneComment);
|
const activity = factory.activity({ albumId, assetId, userId });
|
||||||
|
|
||||||
await sut.create(authStub.admin, {
|
mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId]));
|
||||||
albumId: 'album-id',
|
mocks.activity.create.mockResolvedValue(activity);
|
||||||
assetId: 'asset-id',
|
|
||||||
|
await sut.create(factory.auth({ id: userId }), {
|
||||||
|
albumId,
|
||||||
|
assetId,
|
||||||
type: ReactionType.COMMENT,
|
type: ReactionType.COMMENT,
|
||||||
comment: 'comment',
|
comment: 'comment',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mocks.activity.create).toHaveBeenCalledWith({
|
expect(mocks.activity.create).toHaveBeenCalledWith({
|
||||||
userId: 'admin_id',
|
userId: activity.userId,
|
||||||
albumId: 'album-id',
|
albumId: activity.albumId,
|
||||||
assetId: 'asset-id',
|
assetId: activity.assetId,
|
||||||
comment: 'comment',
|
comment: 'comment',
|
||||||
isLiked: false,
|
isLiked: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail because activity is disabled for the album', async () => {
|
it('should fail because activity is disabled for the album', async () => {
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
|
const [albumId, assetId] = newUuids();
|
||||||
mocks.activity.create.mockResolvedValue(activityStub.oneComment);
|
const activity = factory.activity({ albumId, assetId });
|
||||||
|
|
||||||
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
|
||||||
|
mocks.activity.create.mockResolvedValue(activity);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.create(authStub.admin, {
|
sut.create(factory.auth(), { albumId, assetId, type: ReactionType.COMMENT, comment: 'comment' }),
|
||||||
albumId: 'album-id',
|
|
||||||
assetId: 'asset-id',
|
|
||||||
type: ReactionType.COMMENT,
|
|
||||||
comment: 'comment',
|
|
||||||
}),
|
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a like', async () => {
|
it('should create a like', async () => {
|
||||||
mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set(['album-id']));
|
const [albumId, assetId, userId] = newUuids();
|
||||||
mocks.activity.create.mockResolvedValue(activityStub.liked);
|
const activity = factory.activity({ userId, albumId, assetId, isLiked: true });
|
||||||
|
|
||||||
|
mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId]));
|
||||||
|
mocks.activity.create.mockResolvedValue(activity);
|
||||||
mocks.activity.search.mockResolvedValue([]);
|
mocks.activity.search.mockResolvedValue([]);
|
||||||
|
|
||||||
await sut.create(authStub.admin, {
|
await sut.create(factory.auth({ id: userId }), { albumId, assetId, type: ReactionType.LIKE });
|
||||||
albumId: 'album-id',
|
|
||||||
assetId: 'asset-id',
|
|
||||||
type: ReactionType.LIKE,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mocks.activity.create).toHaveBeenCalledWith({
|
expect(mocks.activity.create).toHaveBeenCalledWith({ userId: activity.userId, albumId, assetId, isLiked: true });
|
||||||
userId: 'admin_id',
|
|
||||||
albumId: 'album-id',
|
|
||||||
assetId: 'asset-id',
|
|
||||||
isLiked: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip if like exists', async () => {
|
it('should skip if like exists', async () => {
|
||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
|
const [albumId, assetId] = newUuids();
|
||||||
mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set(['album-id']));
|
const activity = factory.activity({ albumId, assetId, isLiked: true });
|
||||||
mocks.activity.search.mockResolvedValue([activityStub.liked]);
|
|
||||||
|
|
||||||
await sut.create(authStub.admin, {
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
|
||||||
albumId: 'album-id',
|
mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId]));
|
||||||
assetId: 'asset-id',
|
mocks.activity.search.mockResolvedValue([activity]);
|
||||||
type: ReactionType.LIKE,
|
|
||||||
});
|
await sut.create(factory.auth(), { albumId, assetId, type: ReactionType.LIKE });
|
||||||
|
|
||||||
expect(mocks.activity.create).not.toHaveBeenCalled();
|
expect(mocks.activity.create).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -157,20 +137,29 @@ describe(ActivityService.name, () => {
|
|||||||
|
|
||||||
describe('delete', () => {
|
describe('delete', () => {
|
||||||
it('should require access', async () => {
|
it('should require access', async () => {
|
||||||
await expect(sut.delete(authStub.admin, activityStub.oneComment.id)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.delete(factory.auth(), newUuid())).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.activity.delete).not.toHaveBeenCalled();
|
expect(mocks.activity.delete).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should let the activity owner delete a comment', async () => {
|
it('should let the activity owner delete a comment', async () => {
|
||||||
mocks.access.activity.checkOwnerAccess.mockResolvedValue(new Set(['activity-id']));
|
const activity = factory.activity();
|
||||||
await sut.delete(authStub.admin, 'activity-id');
|
|
||||||
expect(mocks.activity.delete).toHaveBeenCalledWith('activity-id');
|
mocks.access.activity.checkOwnerAccess.mockResolvedValue(new Set([activity.id]));
|
||||||
|
|
||||||
|
await sut.delete(factory.auth(), activity.id);
|
||||||
|
|
||||||
|
expect(mocks.activity.delete).toHaveBeenCalledWith(activity.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should let the album owner delete a comment', async () => {
|
it('should let the album owner delete a comment', async () => {
|
||||||
mocks.access.activity.checkAlbumOwnerAccess.mockResolvedValue(new Set(['activity-id']));
|
const activity = factory.activity();
|
||||||
await sut.delete(authStub.admin, 'activity-id');
|
|
||||||
expect(mocks.activity.delete).toHaveBeenCalledWith('activity-id');
|
mocks.access.activity.checkAlbumOwnerAccess.mockResolvedValue(new Set([activity.id]));
|
||||||
|
|
||||||
|
await sut.delete(factory.auth(), activity.id);
|
||||||
|
|
||||||
|
expect(mocks.activity.delete).toHaveBeenCalledWith(activity.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Insertable, Kysely } from 'kysely';
|
import { Insertable, Kysely } from 'kysely';
|
||||||
import { randomBytes, randomUUID } from 'node:crypto';
|
import { randomBytes } from 'node:crypto';
|
||||||
import { Writable } from 'node:stream';
|
import { Writable } from 'node:stream';
|
||||||
import { Assets, DB, Partners, Sessions, Users } from 'src/db';
|
import { Assets, DB, Partners, Sessions, Users } from 'src/db';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
@ -36,6 +36,7 @@ import { VersionHistoryRepository } from 'src/repositories/version-history.repos
|
|||||||
import { ViewRepository } from 'src/repositories/view-repository';
|
import { ViewRepository } from 'src/repositories/view-repository';
|
||||||
import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock';
|
import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock';
|
||||||
import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
||||||
|
import { newUuid } from 'test/small.factory';
|
||||||
|
|
||||||
class CustomWritable extends Writable {
|
class CustomWritable extends Writable {
|
||||||
private data = '';
|
private data = '';
|
||||||
@ -59,8 +60,6 @@ type User = Partial<Insertable<Users>>;
|
|||||||
type Session = Omit<Insertable<Sessions>, 'token'> & { token?: string };
|
type Session = Omit<Insertable<Sessions>, 'token'> & { token?: string };
|
||||||
type Partner = Insertable<Partners>;
|
type Partner = Insertable<Partners>;
|
||||||
|
|
||||||
export const newUuid = () => randomUUID() as string;
|
|
||||||
|
|
||||||
export class TestFactory {
|
export class TestFactory {
|
||||||
private assets: Asset[] = [];
|
private assets: Asset[] = [];
|
||||||
private sessions: Session[] = [];
|
private sessions: Session[] = [];
|
||||||
|
42
server/test/fixtures/activity.stub.ts
vendored
42
server/test/fixtures/activity.stub.ts
vendored
@ -1,42 +0,0 @@
|
|||||||
import { ActivityItem } from 'src/types';
|
|
||||||
import { albumStub } from 'test/fixtures/album.stub';
|
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
|
||||||
|
|
||||||
export const activityStub = {
|
|
||||||
oneComment: Object.freeze<ActivityItem>({
|
|
||||||
id: 'activity-1',
|
|
||||||
comment: 'comment',
|
|
||||||
isLiked: false,
|
|
||||||
userId: 'admin_id',
|
|
||||||
user: {
|
|
||||||
id: 'admin_id',
|
|
||||||
name: 'admin',
|
|
||||||
email: 'admin@test.com',
|
|
||||||
profileImagePath: '',
|
|
||||||
profileChangedAt: new Date('2021-01-01'),
|
|
||||||
},
|
|
||||||
assetId: assetStub.image.id,
|
|
||||||
albumId: albumStub.oneAsset.id,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updateId: 'uuid-v7',
|
|
||||||
}),
|
|
||||||
liked: Object.freeze<ActivityItem>({
|
|
||||||
id: 'activity-2',
|
|
||||||
comment: null,
|
|
||||||
isLiked: true,
|
|
||||||
userId: 'admin_id',
|
|
||||||
user: {
|
|
||||||
id: 'admin_id',
|
|
||||||
name: 'admin',
|
|
||||||
email: 'admin@test.com',
|
|
||||||
profileImagePath: '',
|
|
||||||
profileChangedAt: new Date('2021-01-01'),
|
|
||||||
},
|
|
||||||
assetId: assetStub.image.id,
|
|
||||||
albumId: albumStub.oneAsset.id,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updateId: 'uuid-v7',
|
|
||||||
}),
|
|
||||||
};
|
|
54
server/test/small.factory.ts
Normal file
54
server/test/small.factory.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import { AuthUser, User } from 'src/database';
|
||||||
|
import { ActivityItem } from 'src/types';
|
||||||
|
|
||||||
|
export const newUuid = () => randomUUID() as string;
|
||||||
|
export const newUuids = () =>
|
||||||
|
Array.from({ length: 100 })
|
||||||
|
.fill(0)
|
||||||
|
.map(() => newUuid());
|
||||||
|
export const newDate = () => new Date();
|
||||||
|
export const newUpdateId = () => 'uuid-v7';
|
||||||
|
|
||||||
|
const authUser = (authUser: Partial<AuthUser>) => ({
|
||||||
|
id: newUuid(),
|
||||||
|
isAdmin: false,
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'test@immich.cloud',
|
||||||
|
quotaUsageInBytes: 0,
|
||||||
|
quotaSizeInBytes: null,
|
||||||
|
...authUser,
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = (user: Partial<User>) => ({
|
||||||
|
id: newUuid(),
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'test@immich.cloud',
|
||||||
|
profileImagePath: '',
|
||||||
|
profileChangedAt: newDate(),
|
||||||
|
...user,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const factory = {
|
||||||
|
auth: (user: Partial<AuthUser> = {}) => ({
|
||||||
|
user: authUser(user),
|
||||||
|
}),
|
||||||
|
authUser,
|
||||||
|
user,
|
||||||
|
activity: (activity: Partial<ActivityItem> = {}) => {
|
||||||
|
const userId = activity.userId || newUuid();
|
||||||
|
return {
|
||||||
|
id: newUuid(),
|
||||||
|
comment: null,
|
||||||
|
isLiked: false,
|
||||||
|
userId,
|
||||||
|
user: user({ id: userId }),
|
||||||
|
assetId: newUuid(),
|
||||||
|
albumId: newUuid(),
|
||||||
|
createdAt: newDate(),
|
||||||
|
updatedAt: newDate(),
|
||||||
|
updateId: newUpdateId(),
|
||||||
|
...activity,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user