Files
immich/server/src/services/activity.service.spec.ts
Robin Brisa a26d703335 feat(web): display number of likes in asset viewer (#18911)
* feat: display number of likes

* fix: properly decrement like count on unlike

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

* chore: pr feedback

* chore: updated related test

* chore: formatter run

* chore: force numberOfLikes to null in album context to pass lint

* chore: open-api updated

* fix: use undefined, not null

* styling tweaks

* chore: updated sql

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-06-04 12:41:50 -05:00

168 lines
6.1 KiB
TypeScript

import { BadRequestException } from '@nestjs/common';
import { ReactionType } from 'src/dtos/activity.dto';
import { ActivityService } from 'src/services/activity.service';
import { factory, newUuid, newUuids } from 'test/small.factory';
import { newTestService, ServiceMocks } from 'test/utils';
describe(ActivityService.name, () => {
let sut: ActivityService;
let mocks: ServiceMocks;
beforeEach(() => {
({ sut, mocks } = newTestService(ActivityService));
});
it('should work', () => {
expect(sut).toBeDefined();
});
describe('getAll', () => {
it('should get all', async () => {
const [albumId, assetId, userId] = newUuids();
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
mocks.activity.search.mockResolvedValue([]);
await expect(sut.getAll(factory.auth({ user: { id: userId } }), { assetId, albumId })).resolves.toEqual([]);
expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: undefined });
});
it('should filter by type=like', async () => {
const [albumId, assetId, userId] = newUuids();
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
mocks.activity.search.mockResolvedValue([]);
await expect(
sut.getAll(factory.auth({ user: { id: userId } }), { assetId, albumId, type: ReactionType.LIKE }),
).resolves.toEqual([]);
expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: true });
});
it('should filter by type=comment', async () => {
const [albumId, assetId] = newUuids();
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
mocks.activity.search.mockResolvedValue([]);
await expect(sut.getAll(factory.auth(), { assetId, albumId, type: ReactionType.COMMENT })).resolves.toEqual([]);
expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: false });
});
});
describe('getStatistics', () => {
it('should get the comment and like count', async () => {
const [albumId, assetId] = newUuids();
mocks.activity.getStatistics.mockResolvedValue({ comments: 1, likes: 3 });
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
await expect(sut.getStatistics(factory.auth(), { assetId, albumId })).resolves.toEqual({ comments: 1, likes: 3 });
});
});
describe('addComment', () => {
it('should require access to the album', async () => {
const [albumId, assetId] = newUuids();
await expect(
sut.create(factory.auth(), { albumId, assetId, type: ReactionType.COMMENT, comment: 'comment' }),
).rejects.toBeInstanceOf(BadRequestException);
});
it('should create a comment', async () => {
const [albumId, assetId, userId] = newUuids();
const activity = factory.activity({ albumId, assetId, userId });
mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId]));
mocks.activity.create.mockResolvedValue(activity);
await sut.create(factory.auth({ user: { id: userId } }), {
albumId,
assetId,
type: ReactionType.COMMENT,
comment: 'comment',
});
expect(mocks.activity.create).toHaveBeenCalledWith({
userId: activity.userId,
albumId: activity.albumId,
assetId: activity.assetId,
comment: 'comment',
isLiked: false,
});
});
it('should fail because activity is disabled for the album', async () => {
const [albumId, assetId] = newUuids();
const activity = factory.activity({ albumId, assetId });
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
mocks.activity.create.mockResolvedValue(activity);
await expect(
sut.create(factory.auth(), { albumId, assetId, type: ReactionType.COMMENT, comment: 'comment' }),
).rejects.toBeInstanceOf(BadRequestException);
});
it('should create a like', async () => {
const [albumId, assetId, userId] = newUuids();
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([]);
await sut.create(factory.auth({ user: { id: userId } }), { albumId, assetId, type: ReactionType.LIKE });
expect(mocks.activity.create).toHaveBeenCalledWith({ userId: activity.userId, albumId, assetId, isLiked: true });
});
it('should skip if like exists', async () => {
const [albumId, assetId] = newUuids();
const activity = factory.activity({ albumId, assetId, isLiked: true });
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId]));
mocks.activity.search.mockResolvedValue([activity]);
await sut.create(factory.auth(), { albumId, assetId, type: ReactionType.LIKE });
expect(mocks.activity.create).not.toHaveBeenCalled();
});
});
describe('delete', () => {
it('should require access', async () => {
await expect(sut.delete(factory.auth(), newUuid())).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.activity.delete).not.toHaveBeenCalled();
});
it('should let the activity owner delete a comment', async () => {
const activity = factory.activity();
mocks.access.activity.checkOwnerAccess.mockResolvedValue(new Set([activity.id]));
mocks.activity.delete.mockResolvedValue();
await sut.delete(factory.auth(), activity.id);
expect(mocks.activity.delete).toHaveBeenCalledWith(activity.id);
});
it('should let the album owner delete a comment', async () => {
const activity = factory.activity();
mocks.access.activity.checkAlbumOwnerAccess.mockResolvedValue(new Set([activity.id]));
mocks.activity.delete.mockResolvedValue();
await sut.delete(factory.auth(), activity.id);
expect(mocks.activity.delete).toHaveBeenCalledWith(activity.id);
});
});
});