import { AssetFileUploadResponseDto, LoginResponseDto, MemoryResponseDto, MemoryType, createMemory, getMemory, } from '@immich/sdk'; import { createUserDto, uuidDto } from 'src/fixtures'; import { errorDto } from 'src/responses'; import { app, asBearerAuth, utils } from 'src/utils'; import request from 'supertest'; import { beforeAll, describe, expect, it } from 'vitest'; describe('/memories', () => { let admin: LoginResponseDto; let user: LoginResponseDto; let adminAsset: AssetFileUploadResponseDto; let userAsset1: AssetFileUploadResponseDto; let userAsset2: AssetFileUploadResponseDto; let userMemory: MemoryResponseDto; beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup(); user = await utils.userSetup(admin.accessToken, createUserDto.user1); [adminAsset, userAsset1, userAsset2] = await Promise.all([ utils.createAsset(admin.accessToken), utils.createAsset(user.accessToken), utils.createAsset(user.accessToken), ]); userMemory = await createMemory( { memoryCreateDto: { type: MemoryType.OnThisDay, memoryAt: new Date(2021).toISOString(), data: { year: 2021 }, assetIds: [], }, }, { headers: asBearerAuth(user.accessToken) }, ); }); describe('GET /memories', () => { it('should require authentication', async () => { const { status, body } = await request(app).get('/memories'); expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); }); }); describe('POST /memories', () => { it('should require authentication', async () => { const { status, body } = await request(app).post('/memories'); expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); }); it('should validate data when type is on this day', async () => { const { status, body } = await request(app) .post('/memories') .set('Authorization', `Bearer ${user.accessToken}`) .send({ type: 'on_this_day', data: {}, memoryAt: new Date(2021).toISOString(), }); expect(status).toBe(400); expect(body).toEqual( errorDto.badRequest(['data.year must be a positive number', 'data.year must be an integer number']), ); }); it('should create a new memory', async () => { const { status, body } = await request(app) .post('/memories') .set('Authorization', `Bearer ${user.accessToken}`) .send({ type: 'on_this_day', data: { year: 2021 }, memoryAt: new Date(2021).toISOString(), }); expect(status).toBe(201); expect(body).toEqual({ id: expect.any(String), type: 'on_this_day', data: { year: 2021 }, createdAt: expect.any(String), updatedAt: expect.any(String), deletedAt: null, seenAt: null, isSaved: false, memoryAt: expect.any(String), ownerId: user.userId, assets: [], }); }); it('should create a new memory (with assets)', async () => { const { status, body } = await request(app) .post('/memories') .set('Authorization', `Bearer ${user.accessToken}`) .send({ type: 'on_this_day', data: { year: 2021 }, memoryAt: new Date(2021).toISOString(), assetIds: [userAsset1.id, userAsset2.id], }); expect(status).toBe(201); expect(body).toMatchObject({ id: expect.any(String), assets: expect.arrayContaining([ expect.objectContaining({ id: userAsset1.id }), expect.objectContaining({ id: userAsset2.id }), ]), }); expect(body.assets).toHaveLength(2); }); it('should create a new memory and ignore assets the user does not have access to', async () => { const { status, body } = await request(app) .post('/memories') .set('Authorization', `Bearer ${user.accessToken}`) .send({ type: 'on_this_day', data: { year: 2021 }, memoryAt: new Date(2021).toISOString(), assetIds: [userAsset1.id, adminAsset.id], }); expect(status).toBe(201); expect(body).toMatchObject({ id: expect.any(String), assets: [expect.objectContaining({ id: userAsset1.id })], }); expect(body.assets).toHaveLength(1); }); }); describe('GET /memories/:id', () => { it('should require authentication', async () => { const { status, body } = await request(app).get(`/memories/${uuidDto.invalid}`); expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); }); it('should require a valid id', async () => { const { status, body } = await request(app) .get(`/memories/${uuidDto.invalid}`) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); }); it('should require access', async () => { const { status, body } = await request(app) .get(`/memories/${userMemory.id}`) .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.noPermission); }); it('should get the memory', async () => { const { status, body } = await request(app) .get(`/memories/${userMemory.id}`) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(200); expect(body).toMatchObject({ id: userMemory.id }); }); }); describe('PUT /memories/:id', () => { it('should require authentication', async () => { const { status, body } = await request(app).put(`/memories/${uuidDto.invalid}`).send({ isSaved: true }); expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); }); it('should require a valid id', async () => { const { status, body } = await request(app) .put(`/memories/${uuidDto.invalid}`) .send({ isSaved: true }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); }); it('should require access', async () => { const { status, body } = await request(app) .put(`/memories/${userMemory.id}`) .send({ isSaved: true }) .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.noPermission); }); it('should update the memory', async () => { const before = await getMemory({ id: userMemory.id }, { headers: asBearerAuth(user.accessToken) }); expect(before.isSaved).toBe(false); const { status, body } = await request(app) .put(`/memories/${userMemory.id}`) .send({ isSaved: true }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(200); expect(body).toMatchObject({ id: userMemory.id, isSaved: true, }); }); }); describe('PUT /memories/:id/assets', () => { it('should require authentication', async () => { const { status, body } = await request(app) .put(`/memories/${userMemory.id}/assets`) .send({ ids: [userAsset1.id] }); expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); }); it('should require a valid id', async () => { const { status, body } = await request(app) .put(`/memories/${uuidDto.invalid}/assets`) .send({ ids: [userAsset1.id] }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); }); it('should require access', async () => { const { status, body } = await request(app) .put(`/memories/${userMemory.id}/assets`) .send({ ids: [userAsset1.id] }) .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.noPermission); }); it('should require a valid asset id', async () => { const { status, body } = await request(app) .put(`/memories/${userMemory.id}/assets`) .send({ ids: [uuidDto.invalid] }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest(['each value in ids must be a UUID'])); }); it('should require asset access', async () => { const { status, body } = await request(app) .put(`/memories/${userMemory.id}/assets`) .send({ ids: [adminAsset.id] }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(1); expect(body[0]).toEqual({ id: adminAsset.id, success: false, error: 'no_permission', }); }); it('should add assets to the memory', async () => { const { status, body } = await request(app) .put(`/memories/${userMemory.id}/assets`) .send({ ids: [userAsset1.id] }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(1); expect(body[0]).toEqual({ id: userAsset1.id, success: true }); }); }); describe('DELETE /memories/:id/assets', () => { it('should require authentication', async () => { const { status, body } = await request(app) .delete(`/memories/${userMemory.id}/assets`) .send({ ids: [userAsset1.id] }); expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); }); it('should require a valid id', async () => { const { status, body } = await request(app) .delete(`/memories/${uuidDto.invalid}/assets`) .send({ ids: [userAsset1.id] }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); }); it('should require access', async () => { const { status, body } = await request(app) .delete(`/memories/${userMemory.id}/assets`) .send({ ids: [userAsset1.id] }) .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.noPermission); }); it('should require a valid asset id', async () => { const { status, body } = await request(app) .delete(`/memories/${userMemory.id}/assets`) .send({ ids: [uuidDto.invalid] }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest(['each value in ids must be a UUID'])); }); it('should only remove assets in the memory', async () => { const { status, body } = await request(app) .delete(`/memories/${userMemory.id}/assets`) .send({ ids: [adminAsset.id] }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(1); expect(body[0]).toEqual({ id: adminAsset.id, success: false, error: 'not_found', }); }); it('should remove assets from the memory', async () => { const { status, body } = await request(app) .delete(`/memories/${userMemory.id}/assets`) .send({ ids: [userAsset1.id] }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(1); expect(body[0]).toEqual({ id: userAsset1.id, success: true }); }); }); describe('DELETE /memories/:id', () => { it('should require authentication', async () => { const { status, body } = await request(app).delete(`/memories/${uuidDto.invalid}`); expect(status).toBe(401); expect(body).toEqual(errorDto.unauthorized); }); it('should require a valid id', async () => { const { status, body } = await request(app) .delete(`/memories/${uuidDto.invalid}`) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); }); it('should require access', async () => { const { status, body } = await request(app) .delete(`/memories/${userMemory.id}`) .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(400); expect(body).toEqual(errorDto.noPermission); }); it('should delete the memory', async () => { const { status } = await request(app) .delete(`/memories/${userMemory.id}`) .send({ isSaved: true }) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(204); }); }); });