mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
refactor: user factories instead of stubs (#17540)
This commit is contained in:
parent
52d4b2fe57
commit
584e5894bf
@ -23,7 +23,7 @@ describe(ActivityService.name, () => {
|
||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set([albumId]));
|
||||
mocks.activity.search.mockResolvedValue([]);
|
||||
|
||||
await expect(sut.getAll(factory.auth({ id: userId }), { assetId, albumId })).resolves.toEqual([]);
|
||||
await expect(sut.getAll(factory.auth({ user: { id: userId } }), { assetId, albumId })).resolves.toEqual([]);
|
||||
|
||||
expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: undefined });
|
||||
});
|
||||
@ -35,7 +35,7 @@ describe(ActivityService.name, () => {
|
||||
mocks.activity.search.mockResolvedValue([]);
|
||||
|
||||
await expect(
|
||||
sut.getAll(factory.auth({ id: userId }), { assetId, albumId, type: ReactionType.LIKE }),
|
||||
sut.getAll(factory.auth({ user: { id: userId } }), { assetId, albumId, type: ReactionType.LIKE }),
|
||||
).resolves.toEqual([]);
|
||||
|
||||
expect(mocks.activity.search).toHaveBeenCalledWith({ assetId, albumId, isLiked: true });
|
||||
@ -80,7 +80,7 @@ describe(ActivityService.name, () => {
|
||||
mocks.access.activity.checkCreateAccess.mockResolvedValue(new Set([albumId]));
|
||||
mocks.activity.create.mockResolvedValue(activity);
|
||||
|
||||
await sut.create(factory.auth({ id: userId }), {
|
||||
await sut.create(factory.auth({ user: { id: userId } }), {
|
||||
albumId,
|
||||
assetId,
|
||||
type: ReactionType.COMMENT,
|
||||
@ -116,7 +116,7 @@ describe(ActivityService.name, () => {
|
||||
mocks.activity.create.mockResolvedValue(activity);
|
||||
mocks.activity.search.mockResolvedValue([]);
|
||||
|
||||
await sut.create(factory.auth({ id: userId }), { albumId, assetId, type: ReactionType.LIKE });
|
||||
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 });
|
||||
});
|
||||
|
@ -54,7 +54,7 @@ describe(ApiKeyService.name, () => {
|
||||
});
|
||||
|
||||
it('should throw an error if the api key does not have sufficient permissions', async () => {
|
||||
const auth = factory.auth({ apiKey: factory.authApiKey({ permissions: [Permission.ASSET_READ] }) });
|
||||
const auth = factory.auth({ apiKey: { permissions: [Permission.ASSET_READ] } });
|
||||
|
||||
await expect(sut.create(auth, { permissions: [Permission.ASSET_UPDATE] })).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
|
@ -88,7 +88,7 @@ describe(AssetService.name, () => {
|
||||
|
||||
it('should get memories with partners with inTimeline enabled', async () => {
|
||||
const partner = factory.partner();
|
||||
const auth = factory.auth({ id: partner.sharedWithId });
|
||||
const auth = factory.auth({ user: { id: partner.sharedWithId } });
|
||||
|
||||
mocks.partner.getAll.mockResolvedValue([partner]);
|
||||
mocks.asset.getByDayOfYear.mockResolvedValue([]);
|
||||
@ -139,7 +139,7 @@ describe(AssetService.name, () => {
|
||||
|
||||
it('should not include partner assets if not in timeline', async () => {
|
||||
const partner = factory.partner({ inTimeline: false });
|
||||
const auth = factory.auth({ id: partner.sharedWithId });
|
||||
const auth = factory.auth({ user: { id: partner.sharedWithId } });
|
||||
|
||||
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
|
||||
mocks.partner.getAll.mockResolvedValue([partner]);
|
||||
@ -151,7 +151,7 @@ describe(AssetService.name, () => {
|
||||
|
||||
it('should include partner assets if in timeline', async () => {
|
||||
const partner = factory.partner({ inTimeline: true });
|
||||
const auth = factory.auth({ id: partner.sharedWithId });
|
||||
const auth = factory.auth({ user: { id: partner.sharedWithId } });
|
||||
|
||||
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
|
||||
mocks.partner.getAll.mockResolvedValue([partner]);
|
||||
|
@ -7,19 +7,18 @@ import { AuthService } from 'src/services/auth.service';
|
||||
import { UserMetadataItem } from 'src/types';
|
||||
import { sharedLinkStub } from 'test/fixtures/shared-link.stub';
|
||||
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { newTestService, ServiceMocks } from 'test/utils';
|
||||
|
||||
const oauthResponse = {
|
||||
const oauthResponse = ({ id, email, name }: { id: string; email: string; name: string }) => ({
|
||||
accessToken: 'cmFuZG9tLWJ5dGVz',
|
||||
userId: 'user-id',
|
||||
userEmail: 'immich@test.com',
|
||||
name: 'immich_name',
|
||||
userId: id,
|
||||
userEmail: email,
|
||||
name,
|
||||
profileImagePath: '',
|
||||
isAdmin: false,
|
||||
shouldChangePassword: false,
|
||||
};
|
||||
});
|
||||
|
||||
// const token = Buffer.from('my-api-key', 'utf8').toString('base64');
|
||||
|
||||
@ -39,15 +38,7 @@ const fixtures = {
|
||||
},
|
||||
};
|
||||
|
||||
const oauthUserWithDefaultQuota = {
|
||||
email,
|
||||
name: ' ',
|
||||
oauthId: sub,
|
||||
quotaSizeInBytes: '1073741824',
|
||||
storageLabel: null,
|
||||
};
|
||||
|
||||
describe('AuthService', () => {
|
||||
describe(AuthService.name, () => {
|
||||
let sut: AuthService;
|
||||
let mocks: ServiceMocks;
|
||||
|
||||
@ -118,14 +109,12 @@ describe('AuthService', () => {
|
||||
|
||||
describe('changePassword', () => {
|
||||
it('should change the password', async () => {
|
||||
const auth = { user: { email: 'test@imimch.com' } } as AuthDto;
|
||||
const user = factory.userAdmin();
|
||||
const auth = factory.auth({ user });
|
||||
const dto = { password: 'old-password', newPassword: 'new-password' };
|
||||
|
||||
mocks.user.getByEmail.mockResolvedValue({
|
||||
email: 'test@immich.com',
|
||||
password: 'hash-password',
|
||||
} as UserAdmin & { password: string });
|
||||
mocks.user.update.mockResolvedValue(userStub.user1);
|
||||
mocks.user.getByEmail.mockResolvedValue({ ...user, password: 'hash-password' });
|
||||
mocks.user.update.mockResolvedValue(user);
|
||||
|
||||
await sut.changePassword(auth, dto);
|
||||
|
||||
@ -331,37 +320,39 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should accept a base64url key', async () => {
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid as any);
|
||||
mocks.user.get.mockResolvedValue(userStub.admin);
|
||||
const user = factory.userAdmin();
|
||||
const sharedLink = { ...sharedLinkStub.valid, user } as any;
|
||||
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLink);
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
|
||||
await expect(
|
||||
sut.authenticate({
|
||||
headers: { 'x-immich-share-key': sharedLinkStub.valid.key.toString('base64url') },
|
||||
headers: { 'x-immich-share-key': sharedLink.key.toString('base64url') },
|
||||
queryParams: {},
|
||||
metadata: { adminRoute: false, sharedLinkRoute: true, uri: 'test' },
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
user: userStub.admin,
|
||||
sharedLink: sharedLinkStub.valid,
|
||||
});
|
||||
expect(mocks.sharedLink.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key);
|
||||
).resolves.toEqual({ user, sharedLink });
|
||||
|
||||
expect(mocks.sharedLink.getByKey).toHaveBeenCalledWith(sharedLink.key);
|
||||
});
|
||||
|
||||
it('should accept a hex key', async () => {
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLinkStub.valid as any);
|
||||
mocks.user.get.mockResolvedValue(userStub.admin);
|
||||
const user = factory.userAdmin();
|
||||
const sharedLink = { ...sharedLinkStub.valid, user } as any;
|
||||
|
||||
mocks.sharedLink.getByKey.mockResolvedValue(sharedLink);
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
|
||||
await expect(
|
||||
sut.authenticate({
|
||||
headers: { 'x-immich-share-key': sharedLinkStub.valid.key.toString('hex') },
|
||||
headers: { 'x-immich-share-key': sharedLink.key.toString('hex') },
|
||||
queryParams: {},
|
||||
metadata: { adminRoute: false, sharedLinkRoute: true, uri: 'test' },
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
user: userStub.admin,
|
||||
sharedLink: sharedLinkStub.valid,
|
||||
});
|
||||
expect(mocks.sharedLink.getByKey).toHaveBeenCalledWith(sharedLinkStub.valid.key);
|
||||
).resolves.toEqual({ user, sharedLink });
|
||||
|
||||
expect(mocks.sharedLink.getByKey).toHaveBeenCalledWith(sharedLink.key);
|
||||
});
|
||||
});
|
||||
|
||||
@ -533,24 +524,28 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should link an existing user', async () => {
|
||||
const user = factory.userAdmin();
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthEnabled);
|
||||
mocks.user.getByEmail.mockResolvedValue(userStub.user1);
|
||||
mocks.user.update.mockResolvedValue(userStub.user1);
|
||||
mocks.user.getByEmail.mockResolvedValue(user);
|
||||
mocks.user.update.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(factory.session());
|
||||
|
||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
||||
oauthResponse,
|
||||
oauthResponse(user),
|
||||
);
|
||||
|
||||
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.user.update).toHaveBeenCalledWith(userStub.user1.id, { oauthId: sub });
|
||||
expect(mocks.user.update).toHaveBeenCalledWith(user.id, { oauthId: sub });
|
||||
});
|
||||
|
||||
it('should not link to a user with a different oauth sub', async () => {
|
||||
const user = factory.userAdmin({ isAdmin: true, oauthId: 'existing-sub' });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithAutoRegister);
|
||||
mocks.user.getByEmail.mockResolvedValueOnce({ ...userStub.user1, oauthId: 'existing-sub' });
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||
mocks.user.getByEmail.mockResolvedValueOnce(user);
|
||||
mocks.user.getAdmin.mockResolvedValue(user);
|
||||
mocks.user.create.mockResolvedValue(user);
|
||||
|
||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).rejects.toThrow(
|
||||
BadRequestException,
|
||||
@ -561,14 +556,16 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should allow auto registering by default', async () => {
|
||||
const user = factory.userAdmin({ oauthId: 'oauth-id' });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled);
|
||||
mocks.user.getByEmail.mockResolvedValue(void 0);
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||
mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true }));
|
||||
mocks.user.create.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(factory.session());
|
||||
|
||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
||||
oauthResponse,
|
||||
oauthResponse(user),
|
||||
);
|
||||
|
||||
expect(mocks.user.getByEmail).toHaveBeenCalledTimes(2); // second call is for domain check before create
|
||||
@ -576,10 +573,12 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should throw an error if user should be auto registered but the email claim does not exist', async () => {
|
||||
const user = factory.userAdmin({ isAdmin: true });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled);
|
||||
mocks.user.getByEmail.mockResolvedValue(void 0);
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||
mocks.user.getAdmin.mockResolvedValue(user);
|
||||
mocks.user.create.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(factory.session());
|
||||
mocks.oauth.getProfile.mockResolvedValue({ sub, email: undefined });
|
||||
|
||||
@ -600,8 +599,10 @@ describe('AuthService', () => {
|
||||
'app.immich:///oauth-callback?code=abc123',
|
||||
]) {
|
||||
it(`should use the mobile redirect override for a url of ${url}`, async () => {
|
||||
const user = factory.userAdmin();
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithMobileOverride);
|
||||
mocks.user.getByOAuthId.mockResolvedValue(userStub.user1);
|
||||
mocks.user.getByOAuthId.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(factory.session());
|
||||
|
||||
await sut.callback({ url }, loginDetails);
|
||||
@ -611,86 +612,97 @@ describe('AuthService', () => {
|
||||
}
|
||||
|
||||
it('should use the default quota', async () => {
|
||||
const user = factory.userAdmin({ oauthId: 'oauth-id' });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota);
|
||||
mocks.user.getByEmail.mockResolvedValue(void 0);
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||
mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true }));
|
||||
mocks.user.create.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(factory.session());
|
||||
|
||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
||||
oauthResponse,
|
||||
oauthResponse(user),
|
||||
);
|
||||
|
||||
expect(mocks.user.create).toHaveBeenCalledWith({ ...oauthUserWithDefaultQuota, quotaSizeInBytes: 1_073_741_824 });
|
||||
expect(mocks.user.create).toHaveBeenCalledWith(expect.objectContaining({ quotaSizeInBytes: 1_073_741_824 }));
|
||||
});
|
||||
|
||||
it('should ignore an invalid storage quota', async () => {
|
||||
const user = factory.userAdmin({ oauthId: 'oauth-id' });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota);
|
||||
mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_quota: 'abc' });
|
||||
mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true }));
|
||||
mocks.user.getByEmail.mockResolvedValue(void 0);
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||
mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: 'abc' });
|
||||
mocks.user.create.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(factory.session());
|
||||
|
||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
||||
oauthResponse,
|
||||
oauthResponse(user),
|
||||
);
|
||||
|
||||
expect(mocks.user.create).toHaveBeenCalledWith({ ...oauthUserWithDefaultQuota, quotaSizeInBytes: 1_073_741_824 });
|
||||
expect(mocks.user.create).toHaveBeenCalledWith(expect.objectContaining({ quotaSizeInBytes: 1_073_741_824 }));
|
||||
});
|
||||
|
||||
it('should ignore a negative quota', async () => {
|
||||
const user = factory.userAdmin({ oauthId: 'oauth-id' });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota);
|
||||
mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_quota: -5 });
|
||||
mocks.user.getAdmin.mockResolvedValue(user);
|
||||
mocks.user.getByEmail.mockResolvedValue(void 0);
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||
mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: -5 });
|
||||
mocks.user.create.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(factory.session());
|
||||
|
||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
||||
oauthResponse,
|
||||
oauthResponse(user),
|
||||
);
|
||||
|
||||
expect(mocks.user.create).toHaveBeenCalledWith({ ...oauthUserWithDefaultQuota, quotaSizeInBytes: 1_073_741_824 });
|
||||
expect(mocks.user.create).toHaveBeenCalledWith(expect.objectContaining({ quotaSizeInBytes: 1_073_741_824 }));
|
||||
});
|
||||
|
||||
it('should not set quota for 0 quota', async () => {
|
||||
const user = factory.userAdmin({ oauthId: 'oauth-id' });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota);
|
||||
mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_quota: 0 });
|
||||
mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true }));
|
||||
mocks.user.getByEmail.mockResolvedValue(void 0);
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||
mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: 0 });
|
||||
mocks.user.create.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(factory.session());
|
||||
|
||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
||||
oauthResponse,
|
||||
oauthResponse(user),
|
||||
);
|
||||
|
||||
expect(mocks.user.create).toHaveBeenCalledWith({
|
||||
email,
|
||||
email: user.email,
|
||||
name: ' ',
|
||||
oauthId: sub,
|
||||
oauthId: user.oauthId,
|
||||
quotaSizeInBytes: null,
|
||||
storageLabel: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use a valid storage quota', async () => {
|
||||
const user = factory.userAdmin({ oauthId: 'oauth-id' });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.oauthWithStorageQuota);
|
||||
mocks.oauth.getProfile.mockResolvedValue({ sub: user.oauthId, email: user.email, immich_quota: 5 });
|
||||
mocks.user.getByEmail.mockResolvedValue(void 0);
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.user1);
|
||||
mocks.user.create.mockResolvedValue(userStub.user1);
|
||||
mocks.oauth.getProfile.mockResolvedValue({ sub, email, immich_quota: 5 });
|
||||
mocks.user.getAdmin.mockResolvedValue(factory.userAdmin({ isAdmin: true }));
|
||||
mocks.user.getByOAuthId.mockResolvedValue(void 0);
|
||||
mocks.user.create.mockResolvedValue(user);
|
||||
mocks.session.create.mockResolvedValue(factory.session());
|
||||
|
||||
await expect(sut.callback({ url: 'http://immich/auth/login?code=abc123' }, loginDetails)).resolves.toEqual(
|
||||
oauthResponse,
|
||||
oauthResponse(user),
|
||||
);
|
||||
|
||||
expect(mocks.user.create).toHaveBeenCalledWith({
|
||||
email,
|
||||
email: user.email,
|
||||
name: ' ',
|
||||
oauthId: sub,
|
||||
oauthId: user.oauthId,
|
||||
quotaSizeInBytes: 5_368_709_120,
|
||||
storageLabel: null,
|
||||
});
|
||||
@ -699,12 +711,11 @@ describe('AuthService', () => {
|
||||
|
||||
describe('link', () => {
|
||||
it('should link an account', async () => {
|
||||
const authUser = factory.authUser();
|
||||
const authApiKey = factory.authApiKey({ permissions: [] });
|
||||
const auth = { user: authUser, apiKey: authApiKey };
|
||||
const user = factory.userAdmin();
|
||||
const auth = factory.auth({ apiKey: { permissions: [] }, user });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled);
|
||||
mocks.user.update.mockResolvedValue(userStub.user1);
|
||||
mocks.user.update.mockResolvedValue(user);
|
||||
|
||||
await sut.link(auth, { url: 'http://immich/user-settings?code=abc123' });
|
||||
|
||||
@ -729,12 +740,11 @@ describe('AuthService', () => {
|
||||
|
||||
describe('unlink', () => {
|
||||
it('should unlink an account', async () => {
|
||||
const authUser = factory.authUser();
|
||||
const authApiKey = factory.authApiKey({ permissions: [] });
|
||||
const auth = { user: authUser, apiKey: authApiKey };
|
||||
const user = factory.userAdmin();
|
||||
const auth = factory.auth({ user, apiKey: { permissions: [] } });
|
||||
|
||||
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.enabled);
|
||||
mocks.user.update.mockResolvedValue(userStub.user1);
|
||||
mocks.user.update.mockResolvedValue(user);
|
||||
|
||||
await sut.unlink(auth);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CliService } from 'src/services/cli.service';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { newTestService, ServiceMocks } from 'test/utils';
|
||||
import { describe, it } from 'vitest';
|
||||
|
||||
@ -13,7 +13,7 @@ describe(CliService.name, () => {
|
||||
|
||||
describe('listUsers', () => {
|
||||
it('should list users', async () => {
|
||||
mocks.user.getList.mockResolvedValue([userStub.admin]);
|
||||
mocks.user.getList.mockResolvedValue([factory.userAdmin({ isAdmin: true })]);
|
||||
await expect(sut.listUsers()).resolves.toEqual([expect.objectContaining({ isAdmin: true })]);
|
||||
expect(mocks.user.getList).toHaveBeenCalledWith({ withDeleted: true });
|
||||
});
|
||||
@ -30,8 +30,10 @@ describe(CliService.name, () => {
|
||||
});
|
||||
|
||||
it('should default to a random password', async () => {
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.admin);
|
||||
mocks.user.update.mockResolvedValue(userStub.admin);
|
||||
const admin = factory.userAdmin({ isAdmin: true });
|
||||
|
||||
mocks.user.getAdmin.mockResolvedValue(admin);
|
||||
mocks.user.update.mockResolvedValue(factory.userAdmin({ isAdmin: true }));
|
||||
|
||||
const ask = vitest.fn().mockImplementation(() => {});
|
||||
|
||||
@ -41,13 +43,15 @@ describe(CliService.name, () => {
|
||||
|
||||
expect(response.provided).toBe(false);
|
||||
expect(ask).toHaveBeenCalled();
|
||||
expect(id).toEqual(userStub.admin.id);
|
||||
expect(id).toEqual(admin.id);
|
||||
expect(update.password).toBeDefined();
|
||||
});
|
||||
|
||||
it('should use the supplied password', async () => {
|
||||
mocks.user.getAdmin.mockResolvedValue(userStub.admin);
|
||||
mocks.user.update.mockResolvedValue(userStub.admin);
|
||||
const admin = factory.userAdmin({ isAdmin: true });
|
||||
|
||||
mocks.user.getAdmin.mockResolvedValue(admin);
|
||||
mocks.user.update.mockResolvedValue(admin);
|
||||
|
||||
const ask = vitest.fn().mockResolvedValue('new-password');
|
||||
|
||||
@ -57,7 +61,7 @@ describe(CliService.name, () => {
|
||||
|
||||
expect(response.provided).toBe(true);
|
||||
expect(ask).toHaveBeenCalled();
|
||||
expect(id).toEqual(userStub.admin.id);
|
||||
expect(id).toEqual(admin.id);
|
||||
expect(update.password).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
@ -35,7 +35,7 @@ describe(MapService.name, () => {
|
||||
|
||||
it('should include partner assets', async () => {
|
||||
const partner = factory.partner();
|
||||
const auth = factory.auth({ id: partner.sharedWithId });
|
||||
const auth = factory.auth({ user: { id: partner.sharedWithId } });
|
||||
|
||||
const asset = assetStub.withLocation;
|
||||
const marker = {
|
||||
|
@ -24,7 +24,7 @@ describe(MemoryService.name, () => {
|
||||
|
||||
mocks.memory.search.mockResolvedValue([memory1, memory2]);
|
||||
|
||||
await expect(sut.search(factory.auth({ id: userId }), {})).resolves.toEqual(
|
||||
await expect(sut.search(factory.auth({ user: { id: userId } }), {})).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: memory1.id, assets: [expect.objectContaining({ id: asset.id })] }),
|
||||
expect.objectContaining({ id: memory2.id, assets: [] }),
|
||||
@ -60,7 +60,9 @@ describe(MemoryService.name, () => {
|
||||
mocks.memory.get.mockResolvedValue(memory);
|
||||
mocks.access.memory.checkOwnerAccess.mockResolvedValue(new Set([memory.id]));
|
||||
|
||||
await expect(sut.get(factory.auth({ id: userId }), memory.id)).resolves.toMatchObject({ id: memory.id });
|
||||
await expect(sut.get(factory.auth({ user: { id: userId } }), memory.id)).resolves.toMatchObject({
|
||||
id: memory.id,
|
||||
});
|
||||
|
||||
expect(mocks.memory.get).toHaveBeenCalledWith(memory.id);
|
||||
expect(mocks.access.memory.checkOwnerAccess).toHaveBeenCalledWith(memory.ownerId, new Set([memory.id]));
|
||||
@ -75,7 +77,7 @@ describe(MemoryService.name, () => {
|
||||
mocks.memory.create.mockResolvedValue(memory);
|
||||
|
||||
await expect(
|
||||
sut.create(factory.auth({ id: userId }), {
|
||||
sut.create(factory.auth({ user: { id: userId } }), {
|
||||
type: memory.type,
|
||||
data: memory.data,
|
||||
memoryAt: memory.memoryAt,
|
||||
@ -105,7 +107,7 @@ describe(MemoryService.name, () => {
|
||||
mocks.memory.create.mockResolvedValue(memory);
|
||||
|
||||
await expect(
|
||||
sut.create(factory.auth({ id: userId }), {
|
||||
sut.create(factory.auth({ user: { id: userId } }), {
|
||||
type: memory.type,
|
||||
data: memory.data,
|
||||
assetIds: memory.assets.map((asset) => asset.id),
|
||||
|
@ -22,7 +22,7 @@ describe(PartnerService.name, () => {
|
||||
const user2 = factory.user();
|
||||
const sharedWithUser2 = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||
const sharedWithUser1 = factory.partner({ sharedBy: user2, sharedWith: user1 });
|
||||
const auth = factory.auth({ id: user1.id });
|
||||
const auth = factory.auth({ user: { id: user1.id } });
|
||||
|
||||
mocks.partner.getAll.mockResolvedValue([sharedWithUser1, sharedWithUser2]);
|
||||
|
||||
@ -35,7 +35,7 @@ describe(PartnerService.name, () => {
|
||||
const user2 = factory.user();
|
||||
const sharedWithUser2 = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||
const sharedWithUser1 = factory.partner({ sharedBy: user2, sharedWith: user1 });
|
||||
const auth = factory.auth({ id: user1.id });
|
||||
const auth = factory.auth({ user: { id: user1.id } });
|
||||
|
||||
mocks.partner.getAll.mockResolvedValue([sharedWithUser1, sharedWithUser2]);
|
||||
await expect(sut.search(auth, { direction: PartnerDirection.SharedWith })).resolves.toBeDefined();
|
||||
@ -48,7 +48,7 @@ describe(PartnerService.name, () => {
|
||||
const user1 = factory.user();
|
||||
const user2 = factory.user();
|
||||
const partner = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||
const auth = factory.auth({ id: user1.id });
|
||||
const auth = factory.auth({ user: { id: user1.id } });
|
||||
|
||||
mocks.partner.get.mockResolvedValue(void 0);
|
||||
mocks.partner.create.mockResolvedValue(partner);
|
||||
@ -65,7 +65,7 @@ describe(PartnerService.name, () => {
|
||||
const user1 = factory.user();
|
||||
const user2 = factory.user();
|
||||
const partner = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||
const auth = factory.auth({ id: user1.id });
|
||||
const auth = factory.auth({ user: { id: user1.id } });
|
||||
|
||||
mocks.partner.get.mockResolvedValue(partner);
|
||||
|
||||
@ -80,7 +80,7 @@ describe(PartnerService.name, () => {
|
||||
const user1 = factory.user();
|
||||
const user2 = factory.user();
|
||||
const partner = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||
const auth = factory.auth({ id: user1.id });
|
||||
const auth = factory.auth({ user: { id: user1.id } });
|
||||
|
||||
mocks.partner.get.mockResolvedValue(partner);
|
||||
|
||||
@ -113,7 +113,7 @@ describe(PartnerService.name, () => {
|
||||
const user1 = factory.user();
|
||||
const user2 = factory.user();
|
||||
const partner = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||
const auth = factory.auth({ id: user1.id });
|
||||
const auth = factory.auth({ user: { id: user1.id } });
|
||||
|
||||
mocks.access.partner.checkUpdateAccess.mockResolvedValue(new Set([user2.id]));
|
||||
mocks.partner.update.mockResolvedValue(partner);
|
||||
|
@ -1280,7 +1280,7 @@ describe(PersonService.name, () => {
|
||||
|
||||
describe('mapFace', () => {
|
||||
it('should map a face', () => {
|
||||
const authDto = factory.auth({ id: faceStub.face1.person.ownerId });
|
||||
const authDto = factory.auth({ user: { id: faceStub.face1.person.ownerId } });
|
||||
expect(mapFaces(faceStub.face1, authDto)).toEqual({
|
||||
boundingBoxX1: 0,
|
||||
boundingBoxX2: 1,
|
||||
|
@ -7,6 +7,7 @@ import { albumStub } from 'test/fixtures/album.stub';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { sharedLinkResponseStub, sharedLinkStub } from 'test/fixtures/shared-link.stub';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { newTestService, ServiceMocks } from 'test/utils';
|
||||
|
||||
describe(SharedLinkService.name, () => {
|
||||
@ -46,7 +47,13 @@ describe(SharedLinkService.name, () => {
|
||||
});
|
||||
|
||||
it('should not return metadata', async () => {
|
||||
const authDto = authStub.adminSharedLinkNoExif;
|
||||
const authDto = factory.auth({
|
||||
sharedLink: {
|
||||
showExif: false,
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
},
|
||||
});
|
||||
mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.readonlyNoExif);
|
||||
await expect(sut.getMine(authDto, {})).resolves.toEqual(sharedLinkResponseStub.readonlyNoMetadata);
|
||||
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authDto.user.id, authDto.sharedLink?.id);
|
||||
@ -208,7 +215,9 @@ describe(SharedLinkService.name, () => {
|
||||
it('should update a shared link', async () => {
|
||||
mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid);
|
||||
mocks.sharedLink.update.mockResolvedValue(sharedLinkStub.valid);
|
||||
|
||||
await sut.update(authStub.user1, sharedLinkStub.valid.id, { allowDownload: false });
|
||||
|
||||
expect(mocks.sharedLink.get).toHaveBeenCalledWith(authStub.user1.user.id, sharedLinkStub.valid.id);
|
||||
expect(mocks.sharedLink.update).toHaveBeenCalledWith({
|
||||
id: sharedLinkStub.valid.id,
|
||||
@ -242,6 +251,7 @@ describe(SharedLinkService.name, () => {
|
||||
describe('addAssets', () => {
|
||||
it('should not work on album shared links', async () => {
|
||||
mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid);
|
||||
|
||||
await expect(sut.addAssets(authStub.admin, 'link-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
@ -273,6 +283,7 @@ describe(SharedLinkService.name, () => {
|
||||
describe('removeAssets', () => {
|
||||
it('should not work on album shared links', async () => {
|
||||
mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.valid);
|
||||
|
||||
await expect(sut.removeAssets(authStub.admin, 'link-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
);
|
||||
@ -297,31 +308,39 @@ describe(SharedLinkService.name, () => {
|
||||
describe('getMetadataTags', () => {
|
||||
it('should return null when auth is not a shared link', async () => {
|
||||
await expect(sut.getMetadataTags(authStub.admin)).resolves.toBe(null);
|
||||
|
||||
expect(mocks.sharedLink.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return null when shared link has a password', async () => {
|
||||
await expect(sut.getMetadataTags(authStub.passwordSharedLink)).resolves.toBe(null);
|
||||
const auth = factory.auth({ user: {}, sharedLink: { password: 'password' } });
|
||||
|
||||
await expect(sut.getMetadataTags(auth)).resolves.toBe(null);
|
||||
|
||||
expect(mocks.sharedLink.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return metadata tags', async () => {
|
||||
mocks.sharedLink.get.mockResolvedValue(sharedLinkStub.individual);
|
||||
|
||||
await expect(sut.getMetadataTags(authStub.adminSharedLink)).resolves.toEqual({
|
||||
description: '1 shared photos & videos',
|
||||
imageUrl: `https://my.immich.app/api/assets/asset-id/thumbnail?key=LCtkaJX4R1O_9D-2lq0STzsPryoL1UdAbyb6Sna1xxmQCSuqU2J1ZUsqt6GR-yGm1s0`,
|
||||
title: 'Public Share',
|
||||
});
|
||||
|
||||
expect(mocks.sharedLink.get).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return metadata tags with a default image path if the asset id is not set', async () => {
|
||||
mocks.sharedLink.get.mockResolvedValue({ ...sharedLinkStub.individual, album: undefined, assets: [] });
|
||||
|
||||
await expect(sut.getMetadataTags(authStub.adminSharedLink)).resolves.toEqual({
|
||||
description: '0 shared photos & videos',
|
||||
imageUrl: `https://my.immich.app/feature-panel.png`,
|
||||
title: 'Public Share',
|
||||
});
|
||||
|
||||
expect(mocks.sharedLink.get).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ import { StorageTemplateService } from 'src/services/storage-template.service';
|
||||
import { albumStub } from 'test/fixtures/album.stub';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
|
||||
|
||||
const motionAsset = assetStub.storageAsset({});
|
||||
@ -426,15 +427,16 @@ describe(StorageTemplateService.name, () => {
|
||||
});
|
||||
|
||||
it('should use the user storage label', async () => {
|
||||
const asset = assetStub.storageAsset();
|
||||
const user = factory.userAdmin({ storageLabel: 'label-1' });
|
||||
const asset = assetStub.storageAsset({ ownerId: user.id });
|
||||
mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset]));
|
||||
mocks.user.getList.mockResolvedValue([userStub.storageLabel]);
|
||||
mocks.user.getList.mockResolvedValue([user]);
|
||||
mocks.move.create.mockResolvedValue({
|
||||
id: '123',
|
||||
entityId: asset.id,
|
||||
pathType: AssetPathType.ORIGINAL,
|
||||
oldPath: asset.originalPath,
|
||||
newPath: `upload/library/user-id/2023/2023-02-23/${asset.originalFileName}`,
|
||||
newPath: `upload/library/${user.storageLabel}/2023/2023-02-23/${asset.originalFileName}`,
|
||||
});
|
||||
|
||||
await sut.handleMigration();
|
||||
@ -442,11 +444,11 @@ describe(StorageTemplateService.name, () => {
|
||||
expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled();
|
||||
expect(mocks.storage.rename).toHaveBeenCalledWith(
|
||||
'/original/path.jpg',
|
||||
`upload/library/label-1/2022/2022-06-19/${asset.originalFileName}`,
|
||||
`upload/library/${user.storageLabel}/2022/2022-06-19/${asset.originalFileName}`,
|
||||
);
|
||||
expect(mocks.asset.update).toHaveBeenCalledWith({
|
||||
id: asset.id,
|
||||
originalPath: `upload/library/label-1/2022/2022-06-19/${asset.originalFileName}`,
|
||||
originalPath: `upload/library/${user.storageLabel}/2022/2022-06-19/${asset.originalFileName}`,
|
||||
});
|
||||
});
|
||||
|
||||
@ -551,98 +553,106 @@ describe(StorageTemplateService.name, () => {
|
||||
|
||||
describe('file rename correctness', () => {
|
||||
it('should not create double extensions when filename has lower extension', async () => {
|
||||
const user = factory.userAdmin({ storageLabel: 'label-1' });
|
||||
const asset = assetStub.storageAsset({
|
||||
originalPath: 'upload/library/user-id/2022/2022-06-19/IMG_7065.heic',
|
||||
ownerId: user.id,
|
||||
originalPath: `upload/library/${user.id}/2022/2022-06-19/IMG_7065.heic`,
|
||||
originalFileName: 'IMG_7065.HEIC',
|
||||
});
|
||||
mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset]));
|
||||
mocks.user.getList.mockResolvedValue([userStub.storageLabel]);
|
||||
mocks.user.getList.mockResolvedValue([user]);
|
||||
mocks.move.create.mockResolvedValue({
|
||||
id: '123',
|
||||
entityId: asset.id,
|
||||
pathType: AssetPathType.ORIGINAL,
|
||||
oldPath: 'upload/library/user-id/2022/2022-06-19/IMG_7065.heic',
|
||||
newPath: 'upload/library/user-id/2023/2023-02-23/IMG_7065.heic',
|
||||
oldPath: `upload/library/${user.id}/2022/2022-06-19/IMG_7065.heic`,
|
||||
newPath: `upload/library/${user.id}/2023/2023-02-23/IMG_7065.heic`,
|
||||
});
|
||||
|
||||
await sut.handleMigration();
|
||||
|
||||
expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled();
|
||||
expect(mocks.storage.rename).toHaveBeenCalledWith(
|
||||
'upload/library/user-id/2022/2022-06-19/IMG_7065.heic',
|
||||
'upload/library/label-1/2022/2022-06-19/IMG_7065.heic',
|
||||
`upload/library/${user.id}/2022/2022-06-19/IMG_7065.heic`,
|
||||
`upload/library/${user.storageLabel}/2022/2022-06-19/IMG_7065.heic`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not create double extensions when filename has uppercase extension', async () => {
|
||||
const user = factory.userAdmin();
|
||||
const asset = assetStub.storageAsset({
|
||||
originalPath: 'upload/library/user-id/2022/2022-06-19/IMG_7065.HEIC',
|
||||
ownerId: user.id,
|
||||
originalPath: `upload/library/${user.id}/2022/2022-06-19/IMG_7065.HEIC`,
|
||||
originalFileName: 'IMG_7065.HEIC',
|
||||
});
|
||||
mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset]));
|
||||
mocks.user.getList.mockResolvedValue([userStub.storageLabel]);
|
||||
mocks.user.getList.mockResolvedValue([user]);
|
||||
mocks.move.create.mockResolvedValue({
|
||||
id: '123',
|
||||
entityId: asset.id,
|
||||
pathType: AssetPathType.ORIGINAL,
|
||||
oldPath: 'upload/library/user-id/2022/2022-06-19/IMG_7065.HEIC',
|
||||
newPath: 'upload/library/user-id/2023/2023-02-23/IMG_7065.heic',
|
||||
oldPath: `upload/library/${user.id}/2022/2022-06-19/IMG_7065.HEIC`,
|
||||
newPath: `upload/library/${user.id}/2023/2023-02-23/IMG_7065.heic`,
|
||||
});
|
||||
|
||||
await sut.handleMigration();
|
||||
|
||||
expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled();
|
||||
expect(mocks.storage.rename).toHaveBeenCalledWith(
|
||||
'upload/library/user-id/2022/2022-06-19/IMG_7065.HEIC',
|
||||
'upload/library/label-1/2022/2022-06-19/IMG_7065.heic',
|
||||
`upload/library/${user.id}/2022/2022-06-19/IMG_7065.HEIC`,
|
||||
`upload/library/${user.id}/2022/2022-06-19/IMG_7065.heic`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should normalize the filename to lowercase (JPEG > jpg)', async () => {
|
||||
const user = factory.userAdmin();
|
||||
const asset = assetStub.storageAsset({
|
||||
originalPath: 'upload/library/user-id/2022/2022-06-19/IMG_7065.JPEG',
|
||||
ownerId: user.id,
|
||||
originalPath: `upload/library/${user.id}/2022/2022-06-19/IMG_7065.JPEG`,
|
||||
originalFileName: 'IMG_7065.JPEG',
|
||||
});
|
||||
mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset]));
|
||||
mocks.user.getList.mockResolvedValue([userStub.storageLabel]);
|
||||
mocks.user.getList.mockResolvedValue([user]);
|
||||
mocks.move.create.mockResolvedValue({
|
||||
id: '123',
|
||||
entityId: asset.id,
|
||||
pathType: AssetPathType.ORIGINAL,
|
||||
oldPath: 'upload/library/user-id/2022/2022-06-19/IMG_7065.JPEG',
|
||||
newPath: 'upload/library/user-id/2023/2023-02-23/IMG_7065.jpg',
|
||||
oldPath: `upload/library/${user.id}/2022/2022-06-19/IMG_7065.JPEG`,
|
||||
newPath: `upload/library/${user.id}/2023/2023-02-23/IMG_7065.jpg`,
|
||||
});
|
||||
|
||||
await sut.handleMigration();
|
||||
|
||||
expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled();
|
||||
expect(mocks.storage.rename).toHaveBeenCalledWith(
|
||||
'upload/library/user-id/2022/2022-06-19/IMG_7065.JPEG',
|
||||
'upload/library/label-1/2022/2022-06-19/IMG_7065.jpg',
|
||||
`upload/library/${user.id}/2022/2022-06-19/IMG_7065.JPEG`,
|
||||
`upload/library/${user.id}/2022/2022-06-19/IMG_7065.jpg`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should normalize the filename to lowercase (JPG > jpg)', async () => {
|
||||
const user = factory.userAdmin();
|
||||
const asset = assetStub.storageAsset({
|
||||
ownerId: user.id,
|
||||
originalPath: 'upload/library/user-id/2022/2022-06-19/IMG_7065.JPG',
|
||||
originalFileName: 'IMG_7065.JPG',
|
||||
});
|
||||
mocks.asset.streamStorageTemplateAssets.mockReturnValue(makeStream([asset]));
|
||||
mocks.user.getList.mockResolvedValue([userStub.storageLabel]);
|
||||
mocks.user.getList.mockResolvedValue([user]);
|
||||
mocks.move.create.mockResolvedValue({
|
||||
id: '123',
|
||||
entityId: asset.id,
|
||||
pathType: AssetPathType.ORIGINAL,
|
||||
oldPath: 'upload/library/user-id/2022/2022-06-19/IMG_7065.JPG',
|
||||
newPath: 'upload/library/user-id/2023/2023-02-23/IMG_7065.jpg',
|
||||
oldPath: `upload/library/${user.id}/2022/2022-06-19/IMG_7065.JPG`,
|
||||
newPath: `upload/library/${user.id}/2023/2023-02-23/IMG_7065.jpg`,
|
||||
});
|
||||
|
||||
await sut.handleMigration();
|
||||
|
||||
expect(mocks.asset.streamStorageTemplateAssets).toHaveBeenCalled();
|
||||
expect(mocks.storage.rename).toHaveBeenCalledWith(
|
||||
'upload/library/user-id/2022/2022-06-19/IMG_7065.JPG',
|
||||
'upload/library/label-1/2022/2022-06-19/IMG_7065.jpg',
|
||||
`upload/library/${user.id}/2022/2022-06-19/IMG_7065.JPG`,
|
||||
`upload/library/${user.id}/2022/2022-06-19/IMG_7065.jpg`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ describe(SyncService.name, () => {
|
||||
describe('getChangesForDeltaSync', () => {
|
||||
it('should return a response requiring a full sync when partners are out of sync', async () => {
|
||||
const partner = factory.partner();
|
||||
const auth = factory.auth({ id: partner.sharedWithId });
|
||||
const auth = factory.auth({ user: { id: partner.sharedWithId } });
|
||||
|
||||
mocks.partner.getAll.mockResolvedValue([partner]);
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { TimeBucketSize } from 'src/repositories/asset.repository';
|
||||
import { TimelineService } from 'src/services/timeline.service';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { factory } from 'test/small.factory';
|
||||
import { newTestService, ServiceMocks } from 'test/utils';
|
||||
|
||||
describe(TimelineService.name, () => {
|
||||
@ -114,15 +115,15 @@ describe(TimelineService.name, () => {
|
||||
mocks.access.album.checkSharedLinkAccess.mockResolvedValue(new Set(['album-id']));
|
||||
mocks.asset.getTimeBucket.mockResolvedValue([assetStub.image]);
|
||||
|
||||
const buckets = await sut.getTimeBucket(
|
||||
{ ...authStub.admin, sharedLink: { ...authStub.adminSharedLink.sharedLink!, showExif: false } },
|
||||
{
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
albumId: 'album-id',
|
||||
},
|
||||
);
|
||||
const auth = factory.auth({ sharedLink: { showExif: false } });
|
||||
|
||||
const buckets = await sut.getTimeBucket(auth, {
|
||||
size: TimeBucketSize.DAY,
|
||||
timeBucket: 'bucket',
|
||||
isArchived: true,
|
||||
albumId: 'album-id',
|
||||
});
|
||||
|
||||
expect(buckets).toEqual([expect.objectContaining({ id: 'asset-id' })]);
|
||||
expect(buckets[0]).not.toHaveProperty('exif');
|
||||
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', {
|
||||
|
@ -29,7 +29,7 @@ describe(UserService.name, () => {
|
||||
describe('getAll', () => {
|
||||
it('admin should get all users', async () => {
|
||||
const user = factory.userAdmin();
|
||||
const auth = factory.auth(user);
|
||||
const auth = factory.auth({ user });
|
||||
|
||||
mocks.user.getList.mockResolvedValue([user]);
|
||||
|
||||
@ -39,14 +39,12 @@ describe(UserService.name, () => {
|
||||
});
|
||||
|
||||
it('non-admin should get all users when publicUsers enabled', async () => {
|
||||
mocks.user.getList.mockResolvedValue([userStub.user1]);
|
||||
const user = factory.userAdmin();
|
||||
const auth = factory.auth({ user });
|
||||
|
||||
await expect(sut.search(authStub.user1)).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
id: authStub.user1.user.id,
|
||||
email: authStub.user1.user.email,
|
||||
}),
|
||||
]);
|
||||
mocks.user.getList.mockResolvedValue([user]);
|
||||
|
||||
await expect(sut.search(auth)).resolves.toEqual([expect.objectContaining({ id: user.id, email: user.email })]);
|
||||
|
||||
expect(mocks.user.getList).toHaveBeenCalledWith({ withDeleted: false });
|
||||
});
|
||||
@ -107,17 +105,19 @@ describe(UserService.name, () => {
|
||||
|
||||
it('should throw an error if the user profile could not be updated with the new image', async () => {
|
||||
const file = { path: '/profile/path' } as Express.Multer.File;
|
||||
mocks.user.get.mockResolvedValue(userStub.profilePath);
|
||||
const user = factory.userAdmin({ profileImagePath: '/path/to/profile.jpg' });
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
mocks.user.update.mockRejectedValue(new InternalServerErrorException('mocked error'));
|
||||
|
||||
await expect(sut.createProfileImage(authStub.admin, file)).rejects.toThrowError(InternalServerErrorException);
|
||||
});
|
||||
|
||||
it('should delete the previous profile image', async () => {
|
||||
const user = factory.userAdmin({ profileImagePath: '/path/to/profile.jpg' });
|
||||
const file = { path: '/profile/path' } as Express.Multer.File;
|
||||
const files = [userStub.profilePath.profileImagePath];
|
||||
const files = [user.profileImagePath];
|
||||
|
||||
mocks.user.get.mockResolvedValue(userStub.profilePath);
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
mocks.user.update.mockResolvedValue({ ...userStub.admin, profileImagePath: file.path });
|
||||
|
||||
await sut.createProfileImage(authStub.admin, file);
|
||||
@ -149,8 +149,10 @@ describe(UserService.name, () => {
|
||||
});
|
||||
|
||||
it('should delete the profile image if user has one', async () => {
|
||||
mocks.user.get.mockResolvedValue(userStub.profilePath);
|
||||
const files = [userStub.profilePath.profileImagePath];
|
||||
const user = factory.userAdmin({ profileImagePath: '/path/to/profile.jpg' });
|
||||
const files = [user.profileImagePath];
|
||||
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
|
||||
await sut.deleteProfileImage(authStub.admin);
|
||||
|
||||
@ -176,9 +178,10 @@ describe(UserService.name, () => {
|
||||
});
|
||||
|
||||
it('should return the profile picture', async () => {
|
||||
mocks.user.get.mockResolvedValue(userStub.profilePath);
|
||||
const user = factory.userAdmin({ profileImagePath: '/path/to/profile.jpg' });
|
||||
mocks.user.get.mockResolvedValue(user);
|
||||
|
||||
await expect(sut.getProfileImage(userStub.profilePath.id)).resolves.toEqual(
|
||||
await expect(sut.getProfileImage(user.id)).resolves.toEqual(
|
||||
new ImmichFileResponse({
|
||||
path: '/path/to/profile.jpg',
|
||||
contentType: 'image/jpeg',
|
||||
@ -186,7 +189,7 @@ describe(UserService.name, () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mocks.user.get).toHaveBeenCalledWith(userStub.profilePath.id, {});
|
||||
expect(mocks.user.get).toHaveBeenCalledWith(user.id, {});
|
||||
});
|
||||
});
|
||||
|
||||
|
20
server/test/fixtures/auth.stub.ts
vendored
20
server/test/fixtures/auth.stub.ts
vendored
@ -52,24 +52,4 @@ export const authStub = {
|
||||
key: Buffer.from('shared-link-key'),
|
||||
} as SharedLinkEntity,
|
||||
}),
|
||||
adminSharedLinkNoExif: Object.freeze<AuthDto>({
|
||||
user: authUser.admin,
|
||||
sharedLink: {
|
||||
id: '123',
|
||||
showExif: false,
|
||||
allowDownload: true,
|
||||
allowUpload: true,
|
||||
key: Buffer.from('shared-link-key'),
|
||||
} as SharedLinkEntity,
|
||||
}),
|
||||
passwordSharedLink: Object.freeze<AuthDto>({
|
||||
user: authUser.admin,
|
||||
sharedLink: {
|
||||
id: '123',
|
||||
allowUpload: false,
|
||||
allowDownload: false,
|
||||
password: 'password-123',
|
||||
showExif: true,
|
||||
} as SharedLinkEntity,
|
||||
}),
|
||||
};
|
||||
|
32
server/test/fixtures/user.stub.ts
vendored
32
server/test/fixtures/user.stub.ts
vendored
@ -57,36 +57,4 @@ export const userStub = {
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
},
|
||||
storageLabel: <UserAdmin>{
|
||||
...authStub.user1.user,
|
||||
status: UserStatus.ACTIVE,
|
||||
profileChangedAt: new Date('2021-01-01'),
|
||||
metadata: [],
|
||||
name: 'immich_name',
|
||||
storageLabel: 'label-1',
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
},
|
||||
profilePath: <UserAdmin>{
|
||||
...authStub.user1.user,
|
||||
status: UserStatus.ACTIVE,
|
||||
profileChangedAt: new Date('2021-01-01'),
|
||||
metadata: [],
|
||||
name: 'immich_name',
|
||||
storageLabel: 'label-1',
|
||||
oauthId: '',
|
||||
shouldChangePassword: false,
|
||||
profileImagePath: '/path/to/profile.jpg',
|
||||
createdAt: new Date('2021-01-01'),
|
||||
deletedAt: null,
|
||||
updatedAt: new Date('2021-01-01'),
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
},
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
ApiKey,
|
||||
Asset,
|
||||
AuthApiKey,
|
||||
AuthSharedLink,
|
||||
AuthUser,
|
||||
Library,
|
||||
Memory,
|
||||
@ -35,12 +36,20 @@ export const newEmbedding = () => {
|
||||
const authFactory = ({
|
||||
apiKey,
|
||||
session,
|
||||
...user
|
||||
}: Partial<AuthUser> & { apiKey?: Partial<AuthApiKey>; session?: { id: string } } = {}) => {
|
||||
sharedLink,
|
||||
user,
|
||||
}: {
|
||||
apiKey?: Partial<AuthApiKey>;
|
||||
session?: { id: string };
|
||||
user?: Partial<UserAdmin>;
|
||||
sharedLink?: Partial<AuthSharedLink>;
|
||||
} = {}) => {
|
||||
const auth: AuthDto = {
|
||||
user: authUserFactory(user),
|
||||
user: authUserFactory(userAdminFactory(user ?? {})),
|
||||
};
|
||||
|
||||
const userId = auth.user.id;
|
||||
|
||||
if (apiKey) {
|
||||
auth.apiKey = authApiKeyFactory(apiKey);
|
||||
}
|
||||
@ -49,24 +58,45 @@ const authFactory = ({
|
||||
auth.session = { id: session.id };
|
||||
}
|
||||
|
||||
if (sharedLink) {
|
||||
auth.sharedLink = authSharedLinkFactory({ ...sharedLink, userId });
|
||||
}
|
||||
|
||||
return auth;
|
||||
};
|
||||
|
||||
const authSharedLinkFactory = (sharedLink: Partial<AuthSharedLink> = {}) => {
|
||||
const {
|
||||
id = newUuid(),
|
||||
expiresAt = null,
|
||||
userId = newUuid(),
|
||||
showExif = true,
|
||||
allowUpload = false,
|
||||
allowDownload = true,
|
||||
password = null,
|
||||
} = sharedLink;
|
||||
|
||||
return { id, expiresAt, userId, showExif, allowUpload, allowDownload, password };
|
||||
};
|
||||
|
||||
const authApiKeyFactory = (apiKey: Partial<AuthApiKey> = {}) => ({
|
||||
id: newUuid(),
|
||||
permissions: [Permission.ALL],
|
||||
...apiKey,
|
||||
});
|
||||
|
||||
const authUserFactory = (authUser: Partial<AuthUser> = {}) => ({
|
||||
id: newUuid(),
|
||||
isAdmin: false,
|
||||
name: 'Test User',
|
||||
email: 'test@immich.cloud',
|
||||
quotaUsageInBytes: 0,
|
||||
quotaSizeInBytes: null,
|
||||
...authUser,
|
||||
});
|
||||
const authUserFactory = (authUser: Partial<AuthUser> = {}) => {
|
||||
const {
|
||||
id = newUuid(),
|
||||
isAdmin = false,
|
||||
name = 'Test User',
|
||||
email = 'test@immich.cloud',
|
||||
quotaUsageInBytes = 0,
|
||||
quotaSizeInBytes = null,
|
||||
} = authUser;
|
||||
|
||||
return { id, isAdmin, name, email, quotaUsageInBytes, quotaSizeInBytes };
|
||||
};
|
||||
|
||||
const partnerFactory = (partner: Partial<Partner> = {}) => {
|
||||
const sharedBy = userFactory(partner.sharedBy || {});
|
||||
@ -112,25 +142,44 @@ const userFactory = (user: Partial<User> = {}) => ({
|
||||
...user,
|
||||
});
|
||||
|
||||
const userAdminFactory = (user: Partial<UserAdmin> = {}) => ({
|
||||
id: newUuid(),
|
||||
name: 'Test User',
|
||||
email: 'test@immich.cloud',
|
||||
profileImagePath: '',
|
||||
profileChangedAt: newDate(),
|
||||
storageLabel: null,
|
||||
shouldChangePassword: false,
|
||||
isAdmin: false,
|
||||
createdAt: newDate(),
|
||||
updatedAt: newDate(),
|
||||
deletedAt: null,
|
||||
oauthId: '',
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
status: UserStatus.ACTIVE,
|
||||
metadata: [],
|
||||
...user,
|
||||
});
|
||||
const userAdminFactory = (user: Partial<UserAdmin> = {}) => {
|
||||
const {
|
||||
id = newUuid(),
|
||||
name = 'Test User',
|
||||
email = 'test@immich.cloud',
|
||||
profileImagePath = '',
|
||||
profileChangedAt = newDate(),
|
||||
storageLabel = null,
|
||||
shouldChangePassword = false,
|
||||
isAdmin = false,
|
||||
createdAt = newDate(),
|
||||
updatedAt = newDate(),
|
||||
deletedAt = null,
|
||||
oauthId = '',
|
||||
quotaSizeInBytes = null,
|
||||
quotaUsageInBytes = 0,
|
||||
status = UserStatus.ACTIVE,
|
||||
metadata = [],
|
||||
} = user;
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
profileImagePath,
|
||||
profileChangedAt,
|
||||
storageLabel,
|
||||
shouldChangePassword,
|
||||
isAdmin,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
deletedAt,
|
||||
oauthId,
|
||||
quotaSizeInBytes,
|
||||
quotaUsageInBytes,
|
||||
status,
|
||||
metadata,
|
||||
};
|
||||
};
|
||||
|
||||
const assetFactory = (asset: Partial<Asset> = {}) => ({
|
||||
id: newUuid(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user