mirror of
https://github.com/immich-app/immich
synced 2026-08-22 13:13:05 +00:00
refactor: e2e tests (#30870)
This commit is contained in:
parent
c63824bcea
commit
7cd0a7d30c
32 changed files with 618 additions and 648 deletions
|
|
@ -1,10 +1,13 @@
|
|||
import { Kysely } from 'kysely';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { AlbumRepository } from 'src/repositories/album.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { UserRepository } from 'src/repositories/user.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { AlbumService } from 'src/services/album.service';
|
||||
import { newMediumService } from 'test/medium.factory';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { getKyselyDB } from 'test/utils';
|
||||
|
||||
let defaultDatabase: Kysely<DB>;
|
||||
|
|
@ -12,7 +15,7 @@ let defaultDatabase: Kysely<DB>;
|
|||
const setup = (db?: Kysely<DB>) => {
|
||||
return newMediumService(AlbumService, {
|
||||
database: db || defaultDatabase,
|
||||
real: [AlbumRepository, UserRepository],
|
||||
real: [AccessRepository, AlbumRepository, AssetRepository, UserRepository],
|
||||
mock: [LoggingRepository],
|
||||
});
|
||||
};
|
||||
|
|
@ -22,16 +25,35 @@ beforeAll(async () => {
|
|||
});
|
||||
|
||||
describe(AlbumService.name, () => {
|
||||
describe('removeAssets', () => {
|
||||
it('should not remove assets from an album of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
const { album } = await ctx.newAlbum({ ownerId: user.id }, [asset.id]);
|
||||
|
||||
await expect(sut.removeAssets(factory.auth({ user: otherUser }), album.id, { ids: [asset.id] })).rejects.toThrow(
|
||||
'Not found or no albumAsset.delete access',
|
||||
);
|
||||
await expect(ctx.get(AlbumRepository).getAssetIds(album.id, [asset.id])).resolves.toContain(asset.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('database triggers', () => {
|
||||
it('should cascade delete an album when the owner is deleted', async () => {
|
||||
const { ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
await ctx.newAlbum({ ownerId: user.id });
|
||||
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
||||
|
||||
await ctx.get(UserRepository).delete({ id: user.id }, true);
|
||||
|
||||
await expect(ctx.database.selectFrom('album').selectAll().execute()).resolves.toEqual([]);
|
||||
await expect(ctx.database.selectFrom('album_user').selectAll().execute()).resolves.toEqual([]);
|
||||
await expect(ctx.database.selectFrom('album').selectAll().where('id', '=', album.id).execute()).resolves.toEqual(
|
||||
[],
|
||||
);
|
||||
await expect(
|
||||
ctx.database.selectFrom('album_user').selectAll().where('albumId', '=', album.id).execute(),
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,6 +24,44 @@ beforeAll(async () => {
|
|||
});
|
||||
|
||||
describe(ApiKeyService.name, () => {
|
||||
describe('getById', () => {
|
||||
it('should not return an api key of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { apiKey } = await sut.create(factory.auth({ user }), { permissions: [Permission.All] });
|
||||
|
||||
await expect(sut.getById(factory.auth({ user: otherUser }), apiKey.id)).rejects.toThrow('API Key not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should not update an api key of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { apiKey } = await sut.create(factory.auth({ user }), { permissions: [Permission.All] });
|
||||
|
||||
await expect(sut.update(factory.auth({ user: otherUser }), apiKey.id, { name: 'new name' })).rejects.toThrow(
|
||||
'API Key not found',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should not delete an api key of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { apiKey } = await sut.create(factory.auth({ user }), { permissions: [Permission.All] });
|
||||
|
||||
await expect(sut.delete(factory.auth({ user: otherUser }), apiKey.id)).rejects.toThrow('API Key not found');
|
||||
await expect(sut.getById(factory.auth({ user }), apiKey.id)).resolves.toEqual(
|
||||
expect.objectContaining({ id: apiKey.id }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rotate', () => {
|
||||
it('should not rotate an api key of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
|
|
|
|||
|
|
@ -45,6 +45,19 @@ beforeAll(async () => {
|
|||
});
|
||||
|
||||
describe(AssetService.name, () => {
|
||||
describe('get', () => {
|
||||
it('should not return an asset of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
|
||||
await expect(sut.get(factory.auth({ user: otherUser }), asset.id)).rejects.toThrow(
|
||||
'Not found or no asset.read access',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStatistics', () => {
|
||||
it('should return stats as numbers, not strings', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
|
|
@ -334,6 +347,17 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should not update an asset of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
|
||||
await expect(sut.update(factory.auth({ user: otherUser }), asset.id, {})).rejects.toThrow(
|
||||
'Not found or no asset.update access',
|
||||
);
|
||||
});
|
||||
|
||||
it('should automatically lock lockable columns', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
ctx.getMock(JobRepository).queue.mockResolvedValue();
|
||||
|
|
|
|||
|
|
@ -34,7 +34,70 @@ const setup = (db?: Kysely<DB>) => {
|
|||
});
|
||||
};
|
||||
|
||||
/** A memory owned by one user, plus another user's auth to attempt access with */
|
||||
const newMemoryOfAnotherUser = async (ctx: ReturnType<typeof setup>['ctx']) => {
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { memory } = await ctx.newMemory({ ownerId: user.id });
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
|
||||
return { memory, asset, auth: factory.auth({ user }), otherAuth: factory.auth({ user: otherUser }) };
|
||||
};
|
||||
|
||||
describe(MemoryService.name, () => {
|
||||
describe('get', () => {
|
||||
it('should not return a memory of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { memory, otherAuth } = await newMemoryOfAnotherUser(ctx);
|
||||
|
||||
await expect(sut.get(otherAuth, memory.id)).rejects.toThrow('Not found or no memory.read access');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should not update a memory of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { memory, otherAuth } = await newMemoryOfAnotherUser(ctx);
|
||||
|
||||
await expect(sut.update(otherAuth, memory.id, { isSaved: true })).rejects.toThrow(
|
||||
'Not found or no memory.update access',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
it('should not remove a memory of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { memory, auth, otherAuth } = await newMemoryOfAnotherUser(ctx);
|
||||
|
||||
await expect(sut.remove(otherAuth, memory.id)).rejects.toThrow('Not found or no memory.delete access');
|
||||
await expect(sut.get(auth, memory.id)).resolves.toEqual(expect.objectContaining({ id: memory.id }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('addAssets', () => {
|
||||
it('should not add assets to a memory of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { memory, asset, otherAuth } = await newMemoryOfAnotherUser(ctx);
|
||||
|
||||
await expect(sut.addAssets(otherAuth, memory.id, { ids: [asset.id] })).rejects.toThrow(
|
||||
'Not found or no memory.read access',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeAssets', () => {
|
||||
it('should not remove assets from a memory of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { memory, asset, otherAuth } = await newMemoryOfAnotherUser(ctx);
|
||||
await ctx.newMemoryAsset({ memoryId: memory.id, assetId: asset.id });
|
||||
|
||||
await expect(sut.removeAssets(otherAuth, memory.id, { ids: [asset.id] })).rejects.toThrow(
|
||||
'Not found or no memory.update access',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
defaultDatabase = await getKyselyDB();
|
||||
});
|
||||
|
|
|
|||
44
server/test/medium/specs/services/stack.service.spec.ts
Normal file
44
server/test/medium/specs/services/stack.service.spec.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { Kysely } from 'kysely';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { EventRepository } from 'src/repositories/event.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { StackRepository } from 'src/repositories/stack.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { StackService } from 'src/services/stack.service';
|
||||
import { newMediumService } from 'test/medium.factory';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { getKyselyDB } from 'test/utils';
|
||||
|
||||
let defaultDatabase: Kysely<DB>;
|
||||
|
||||
const setup = (db?: Kysely<DB>) => {
|
||||
return newMediumService(StackService, {
|
||||
database: db || defaultDatabase,
|
||||
real: [AccessRepository, AssetRepository, StackRepository],
|
||||
mock: [EventRepository, LoggingRepository],
|
||||
});
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
defaultDatabase = await getKyselyDB();
|
||||
});
|
||||
|
||||
describe(StackService.name, () => {
|
||||
describe('create', () => {
|
||||
it('should not stack an asset of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
const { asset: otherAsset } = await ctx.newAsset({ ownerId: otherUser.id });
|
||||
|
||||
await expect(sut.create(factory.auth({ user }), { assetIds: [asset.id, otherAsset.id] })).rejects.toThrow(
|
||||
'Not found or no asset.update access',
|
||||
);
|
||||
await expect(
|
||||
ctx.database.selectFrom('stack').selectAll().where('ownerId', '=', user.id).execute(),
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -22,12 +22,61 @@ const setup = (db?: Kysely<DB>) => {
|
|||
});
|
||||
};
|
||||
|
||||
/** A tag owned by one user, plus another user's auth to attempt access with */
|
||||
const newTagOfAnotherUser = async (ctx: ReturnType<typeof setup>['ctx']) => {
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const [tag] = await upsertTags(ctx.get(TagRepository), { userId: user.id, tags: ['tag-1'] });
|
||||
|
||||
return { tag, auth: factory.auth({ user }), otherAuth: factory.auth({ user: otherUser }) };
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
defaultDatabase = await getKyselyDB();
|
||||
});
|
||||
|
||||
describe(TagService.name, () => {
|
||||
describe('get', () => {
|
||||
it('should not return a tag of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { tag, otherAuth } = await newTagOfAnotherUser(ctx);
|
||||
|
||||
await expect(sut.get(otherAuth, tag.id)).rejects.toThrow('Not found or no tag.read access');
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('should not update a tag of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { tag, otherAuth } = await newTagOfAnotherUser(ctx);
|
||||
|
||||
await expect(sut.update(otherAuth, tag.id, { color: '#000000' })).rejects.toThrow(
|
||||
'Not found or no tag.update access',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
it('should not remove a tag of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { tag, auth, otherAuth } = await newTagOfAnotherUser(ctx);
|
||||
|
||||
await expect(sut.remove(otherAuth, tag.id)).rejects.toThrow('Not found or no tag.delete access');
|
||||
await expect(sut.get(auth, tag.id)).resolves.toEqual(expect.objectContaining({ id: tag.id }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('addAssets', () => {
|
||||
it('should not add assets to a tag of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { tag, otherAuth } = await newTagOfAnotherUser(ctx);
|
||||
const { asset } = await ctx.newAsset({ ownerId: otherAuth.user.id });
|
||||
|
||||
await expect(sut.addAssets(otherAuth, tag.id, { ids: [asset.id] })).rejects.toThrow(
|
||||
'Not found or no tag.asset access',
|
||||
);
|
||||
});
|
||||
|
||||
it('should lock exif column', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||
|
|
@ -53,6 +102,22 @@ describe(TagService.name, () => {
|
|||
await expect(ctx.get(TagRepository).getAssetIds(tag.id, [asset.id])).resolves.toContain(asset.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeAssets', () => {
|
||||
it('should not remove assets from a tag of another user', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
ctx.getMock(EventRepository).emit.mockResolvedValue();
|
||||
const { tag, auth, otherAuth } = await newTagOfAnotherUser(ctx);
|
||||
const { asset } = await ctx.newAsset({ ownerId: auth.user.id });
|
||||
await sut.addAssets(auth, tag.id, { ids: [asset.id] });
|
||||
|
||||
await expect(sut.removeAssets(otherAuth, tag.id, { ids: [asset.id] })).rejects.toThrow(
|
||||
'Not found or no tag.asset access',
|
||||
);
|
||||
await expect(ctx.get(TagRepository).getAssetIds(tag.id, [asset.id])).resolves.toContain(asset.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteEmptyTags', () => {
|
||||
it('single tag exists, not connected to any assets, and is deleted', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue