mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
refactor: use factory and kysely types for partner repository (#16812)
This commit is contained in:
parent
83ed03920e
commit
16fd19994b
@ -92,6 +92,17 @@ export type AuthSession = {
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Partner = {
|
||||||
|
sharedById: string;
|
||||||
|
sharedBy: User;
|
||||||
|
sharedWithId: string;
|
||||||
|
sharedWith: User;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
updateId: string;
|
||||||
|
inTimeline: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const columns = {
|
export const columns = {
|
||||||
ackEpoch: (columnName: 'createdAt' | 'updatedAt' | 'deletedAt') =>
|
ackEpoch: (columnName: 'createdAt' | 'updatedAt' | 'deletedAt') =>
|
||||||
sql.raw<string>(`extract(epoch from "${columnName}")::text`).as('ackEpoch'),
|
sql.raw<string>(`extract(epoch from "${columnName}")::text`).as('ackEpoch'),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Transform } from 'class-transformer';
|
import { Transform } from 'class-transformer';
|
||||||
import { IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator';
|
import { IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator';
|
||||||
|
import { User } from 'src/database';
|
||||||
import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
|
import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum';
|
import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum';
|
||||||
@ -52,6 +53,17 @@ export const mapUser = (entity: UserEntity): UserResponseDto => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const mapDatabaseUser = (user: User): UserResponseDto => {
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
profileImagePath: user.profileImagePath,
|
||||||
|
avatarColor: getPreferences(user.email, []).avatar.color,
|
||||||
|
profileChangedAt: user.profileChangedAt,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export class UserAdminSearchDto {
|
export class UserAdminSearchDto {
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
withDeleted?: boolean;
|
withDeleted?: boolean;
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
|
/** @deprecated delete after coming up with a migration workflow for kysely */
|
||||||
@Entity('partners')
|
@Entity('partners')
|
||||||
export class PartnerEntity {
|
export class PartnerEntity {
|
||||||
@PrimaryColumn('uuid')
|
@PrimaryColumn('uuid')
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ExpressionBuilder, Insertable, JoinBuilder, Kysely, Updateable } from 'kysely';
|
import { ExpressionBuilder, Insertable, Kysely, Updateable } from 'kysely';
|
||||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { DB, Partners, Users } from 'src/db';
|
import { columns, Partner } from 'src/database';
|
||||||
|
import { DB, Partners } from 'src/db';
|
||||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { PartnerEntity } from 'src/entities/partner.entity';
|
|
||||||
|
|
||||||
export interface PartnerIds {
|
export interface PartnerIds {
|
||||||
sharedById: string;
|
sharedById: string;
|
||||||
@ -16,23 +16,18 @@ export enum PartnerDirection {
|
|||||||
SharedWith = 'shared-with',
|
SharedWith = 'shared-with',
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const;
|
|
||||||
|
|
||||||
const onSharedBy = (join: JoinBuilder<DB & { sharedBy: Users }, 'partners' | 'sharedBy'>) =>
|
|
||||||
join.onRef('partners.sharedById', '=', 'sharedBy.id').on('sharedBy.deletedAt', 'is', null);
|
|
||||||
|
|
||||||
const onSharedWith = (join: JoinBuilder<DB & { sharedWith: Users }, 'partners' | 'sharedWith'>) =>
|
|
||||||
join.onRef('partners.sharedWithId', '=', 'sharedWith.id').on('sharedWith.deletedAt', 'is', null);
|
|
||||||
|
|
||||||
const withSharedBy = (eb: ExpressionBuilder<DB, 'partners'>) => {
|
const withSharedBy = (eb: ExpressionBuilder<DB, 'partners'>) => {
|
||||||
return jsonObjectFrom(
|
return jsonObjectFrom(
|
||||||
eb.selectFrom('users as sharedBy').select(columns).whereRef('sharedBy.id', '=', 'partners.sharedById'),
|
eb.selectFrom('users as sharedBy').select(columns.userDto).whereRef('sharedBy.id', '=', 'partners.sharedById'),
|
||||||
).as('sharedBy');
|
).as('sharedBy');
|
||||||
};
|
};
|
||||||
|
|
||||||
const withSharedWith = (eb: ExpressionBuilder<DB, 'partners'>) => {
|
const withSharedWith = (eb: ExpressionBuilder<DB, 'partners'>) => {
|
||||||
return jsonObjectFrom(
|
return jsonObjectFrom(
|
||||||
eb.selectFrom('users as sharedWith').select(columns).whereRef('sharedWith.id', '=', 'partners.sharedWithId'),
|
eb
|
||||||
|
.selectFrom('users as sharedWith')
|
||||||
|
.select(columns.userDto)
|
||||||
|
.whereRef('sharedWith.id', '=', 'partners.sharedWithId'),
|
||||||
).as('sharedWith');
|
).as('sharedWith');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,45 +36,33 @@ export class PartnerRepository {
|
|||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getAll(userId: string): Promise<PartnerEntity[]> {
|
getAll(userId: string) {
|
||||||
return this.db
|
return this.builder()
|
||||||
.selectFrom('partners')
|
|
||||||
.innerJoin('users as sharedBy', onSharedBy)
|
|
||||||
.innerJoin('users as sharedWith', onSharedWith)
|
|
||||||
.selectAll('partners')
|
|
||||||
.select(withSharedBy)
|
|
||||||
.select(withSharedWith)
|
|
||||||
.where((eb) => eb.or([eb('sharedWithId', '=', userId), eb('sharedById', '=', userId)]))
|
.where((eb) => eb.or([eb('sharedWithId', '=', userId), eb('sharedById', '=', userId)]))
|
||||||
.execute() as Promise<PartnerEntity[]>;
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
|
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
|
||||||
get({ sharedWithId, sharedById }: PartnerIds): Promise<PartnerEntity | undefined> {
|
get({ sharedWithId, sharedById }: PartnerIds) {
|
||||||
return this.db
|
return this.builder()
|
||||||
.selectFrom('partners')
|
|
||||||
.innerJoin('users as sharedBy', onSharedBy)
|
|
||||||
.innerJoin('users as sharedWith', onSharedWith)
|
|
||||||
.selectAll('partners')
|
|
||||||
.select(withSharedBy)
|
|
||||||
.select(withSharedWith)
|
|
||||||
.where('sharedWithId', '=', sharedWithId)
|
.where('sharedWithId', '=', sharedWithId)
|
||||||
.where('sharedById', '=', sharedById)
|
.where('sharedById', '=', sharedById)
|
||||||
.executeTakeFirst() as unknown as Promise<PartnerEntity | undefined>;
|
.executeTakeFirst() as Promise<Partner | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
|
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
|
||||||
create(values: Insertable<Partners>): Promise<PartnerEntity> {
|
create(values: Insertable<Partners>) {
|
||||||
return this.db
|
return this.db
|
||||||
.insertInto('partners')
|
.insertInto('partners')
|
||||||
.values(values)
|
.values(values)
|
||||||
.returningAll()
|
.returningAll()
|
||||||
.returning(withSharedBy)
|
.returning(withSharedBy)
|
||||||
.returning(withSharedWith)
|
.returning(withSharedWith)
|
||||||
.executeTakeFirstOrThrow() as unknown as Promise<PartnerEntity>;
|
.executeTakeFirstOrThrow() as Promise<Partner>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }, { inTimeline: true }] })
|
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }, { inTimeline: true }] })
|
||||||
update({ sharedWithId, sharedById }: PartnerIds, values: Updateable<Partners>): Promise<PartnerEntity> {
|
update({ sharedWithId, sharedById }: PartnerIds, values: Updateable<Partners>) {
|
||||||
return this.db
|
return this.db
|
||||||
.updateTable('partners')
|
.updateTable('partners')
|
||||||
.set(values)
|
.set(values)
|
||||||
@ -88,15 +71,29 @@ export class PartnerRepository {
|
|||||||
.returningAll()
|
.returningAll()
|
||||||
.returning(withSharedBy)
|
.returning(withSharedBy)
|
||||||
.returning(withSharedWith)
|
.returning(withSharedWith)
|
||||||
.executeTakeFirstOrThrow() as unknown as Promise<PartnerEntity>;
|
.executeTakeFirstOrThrow() as Promise<Partner>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
|
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
|
||||||
async remove({ sharedWithId, sharedById }: PartnerIds): Promise<void> {
|
async remove({ sharedWithId, sharedById }: PartnerIds) {
|
||||||
await this.db
|
await this.db
|
||||||
.deleteFrom('partners')
|
.deleteFrom('partners')
|
||||||
.where('sharedWithId', '=', sharedWithId)
|
.where('sharedWithId', '=', sharedWithId)
|
||||||
.where('sharedById', '=', sharedById)
|
.where('sharedById', '=', sharedById)
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private builder() {
|
||||||
|
return this.db
|
||||||
|
.selectFrom('partners')
|
||||||
|
.innerJoin('users as sharedBy', (join) =>
|
||||||
|
join.onRef('partners.sharedById', '=', 'sharedBy.id').on('sharedBy.deletedAt', 'is', null),
|
||||||
|
)
|
||||||
|
.innerJoin('users as sharedWith', (join) =>
|
||||||
|
join.onRef('partners.sharedWithId', '=', 'sharedWith.id').on('sharedWith.deletedAt', 'is', null),
|
||||||
|
)
|
||||||
|
.selectAll('partners')
|
||||||
|
.select(withSharedBy)
|
||||||
|
.select(withSharedWith);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import { AssetService } from 'src/services/asset.service';
|
|||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { faceStub } from 'test/fixtures/face.stub';
|
import { faceStub } from 'test/fixtures/face.stub';
|
||||||
import { partnerStub } from 'test/fixtures/partner.stub';
|
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
import { userStub } from 'test/fixtures/user.stub';
|
||||||
import { factory } from 'test/small.factory';
|
import { factory } from 'test/small.factory';
|
||||||
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
|
import { makeStream, newTestService, ServiceMocks } from 'test/utils';
|
||||||
@ -88,13 +87,16 @@ describe(AssetService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should get memories with partners with inTimeline enabled', async () => {
|
it('should get memories with partners with inTimeline enabled', async () => {
|
||||||
mocks.partner.getAll.mockResolvedValue([partnerStub.user1ToAdmin1]);
|
const partner = factory.partner();
|
||||||
|
const auth = factory.auth({ id: partner.sharedWithId });
|
||||||
|
|
||||||
|
mocks.partner.getAll.mockResolvedValue([partner]);
|
||||||
mocks.asset.getByDayOfYear.mockResolvedValue([]);
|
mocks.asset.getByDayOfYear.mockResolvedValue([]);
|
||||||
|
|
||||||
await sut.getMemoryLane(authStub.admin, { day: 15, month: 1 });
|
await sut.getMemoryLane(auth, { day: 15, month: 1 });
|
||||||
|
|
||||||
expect(mocks.asset.getByDayOfYear.mock.calls).toEqual([
|
expect(mocks.asset.getByDayOfYear.mock.calls).toEqual([
|
||||||
[[authStub.admin.user.id, userStub.user1.id], { day: 15, month: 1 }],
|
[[auth.user.id, partner.sharedById], { day: 15, month: 1 }],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -136,17 +138,27 @@ describe(AssetService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not include partner assets if not in timeline', async () => {
|
it('should not include partner assets if not in timeline', async () => {
|
||||||
|
const partner = factory.partner({ inTimeline: false });
|
||||||
|
const auth = factory.auth({ id: partner.sharedWithId });
|
||||||
|
|
||||||
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
|
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
|
||||||
mocks.partner.getAll.mockResolvedValue([{ ...partnerStub.user1ToAdmin1, inTimeline: false }]);
|
mocks.partner.getAll.mockResolvedValue([partner]);
|
||||||
await sut.getRandom(authStub.admin, 1);
|
|
||||||
expect(mocks.asset.getRandom).toHaveBeenCalledWith([authStub.admin.user.id], 1);
|
await sut.getRandom(auth, 1);
|
||||||
|
|
||||||
|
expect(mocks.asset.getRandom).toHaveBeenCalledWith([auth.user.id], 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include partner assets if in timeline', async () => {
|
it('should include partner assets if in timeline', async () => {
|
||||||
|
const partner = factory.partner({ inTimeline: true });
|
||||||
|
const auth = factory.auth({ id: partner.sharedWithId });
|
||||||
|
|
||||||
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
|
mocks.asset.getRandom.mockResolvedValue([assetStub.image]);
|
||||||
mocks.partner.getAll.mockResolvedValue([partnerStub.user1ToAdmin1]);
|
mocks.partner.getAll.mockResolvedValue([partner]);
|
||||||
await sut.getRandom(authStub.admin, 1);
|
|
||||||
expect(mocks.asset.getRandom).toHaveBeenCalledWith([userStub.admin.id, userStub.user1.id], 1);
|
await sut.getRandom(auth, 1);
|
||||||
|
|
||||||
|
expect(mocks.asset.getRandom).toHaveBeenCalledWith([auth.user.id, partner.sharedById], 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -154,7 +166,9 @@ describe(AssetService.name, () => {
|
|||||||
it('should allow owner access', async () => {
|
it('should allow owner access', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||||
|
|
||||||
await sut.get(authStub.admin, assetStub.image.id);
|
await sut.get(authStub.admin, assetStub.image.id);
|
||||||
|
|
||||||
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(
|
expect(mocks.access.asset.checkOwnerAccess).toHaveBeenCalledWith(
|
||||||
authStub.admin.user.id,
|
authStub.admin.user.id,
|
||||||
new Set([assetStub.image.id]),
|
new Set([assetStub.image.id]),
|
||||||
@ -164,7 +178,9 @@ describe(AssetService.name, () => {
|
|||||||
it('should allow shared link access', async () => {
|
it('should allow shared link access', async () => {
|
||||||
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
mocks.access.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||||
|
|
||||||
await sut.get(authStub.adminSharedLink, assetStub.image.id);
|
await sut.get(authStub.adminSharedLink, assetStub.image.id);
|
||||||
|
|
||||||
expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
|
expect(mocks.access.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||||
authStub.adminSharedLink.sharedLink?.id,
|
authStub.adminSharedLink.sharedLink?.id,
|
||||||
new Set([assetStub.image.id]),
|
new Set([assetStub.image.id]),
|
||||||
@ -191,7 +207,9 @@ describe(AssetService.name, () => {
|
|||||||
it('should allow partner sharing access', async () => {
|
it('should allow partner sharing access', async () => {
|
||||||
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
mocks.access.asset.checkPartnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||||
|
|
||||||
await sut.get(authStub.admin, assetStub.image.id);
|
await sut.get(authStub.admin, assetStub.image.id);
|
||||||
|
|
||||||
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
expect(mocks.access.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
||||||
authStub.admin.user.id,
|
authStub.admin.user.id,
|
||||||
new Set([assetStub.image.id]),
|
new Set([assetStub.image.id]),
|
||||||
@ -201,7 +219,9 @@ describe(AssetService.name, () => {
|
|||||||
it('should allow shared album access', async () => {
|
it('should allow shared album access', async () => {
|
||||||
mocks.access.asset.checkAlbumAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
mocks.access.asset.checkAlbumAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
mocks.asset.getById.mockResolvedValue(assetStub.image);
|
||||||
|
|
||||||
await sut.get(authStub.admin, assetStub.image.id);
|
await sut.get(authStub.admin, assetStub.image.id);
|
||||||
|
|
||||||
expect(mocks.access.asset.checkAlbumAccess).toHaveBeenCalledWith(
|
expect(mocks.access.asset.checkAlbumAccess).toHaveBeenCalledWith(
|
||||||
authStub.admin.user.id,
|
authStub.admin.user.id,
|
||||||
new Set([assetStub.image.id]),
|
new Set([assetStub.image.id]),
|
||||||
@ -210,17 +230,20 @@ describe(AssetService.name, () => {
|
|||||||
|
|
||||||
it('should throw an error for no access', async () => {
|
it('should throw an error for no access', async () => {
|
||||||
await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.asset.getById).not.toHaveBeenCalled();
|
expect(mocks.asset.getById).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error for an invalid shared link', async () => {
|
it('should throw an error for an invalid shared link', async () => {
|
||||||
await expect(sut.get(authStub.adminSharedLink, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.get(authStub.adminSharedLink, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.access.asset.checkOwnerAccess).not.toHaveBeenCalled();
|
expect(mocks.access.asset.checkOwnerAccess).not.toHaveBeenCalled();
|
||||||
expect(mocks.asset.getById).not.toHaveBeenCalled();
|
expect(mocks.asset.getById).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the asset could not be found', async () => {
|
it('should throw an error if the asset could not be found', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
|
|
||||||
await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.get(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -230,6 +253,7 @@ describe(AssetService.name, () => {
|
|||||||
await expect(sut.update(authStub.admin, 'asset-1', { isArchived: false })).rejects.toBeInstanceOf(
|
await expect(sut.update(authStub.admin, 'asset-1', { isArchived: false })).rejects.toBeInstanceOf(
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mocks.asset.update).not.toHaveBeenCalled();
|
expect(mocks.asset.update).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -259,6 +283,7 @@ describe(AssetService.name, () => {
|
|||||||
mocks.asset.update.mockResolvedValueOnce(assetStub.image);
|
mocks.asset.update.mockResolvedValueOnce(assetStub.image);
|
||||||
|
|
||||||
await sut.update(authStub.admin, 'asset-1', { rating: 3 });
|
await sut.update(authStub.admin, 'asset-1', { rating: 3 });
|
||||||
|
|
||||||
expect(mocks.asset.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', rating: 3 });
|
expect(mocks.asset.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', rating: 3 });
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -401,12 +426,15 @@ describe(AssetService.name, () => {
|
|||||||
|
|
||||||
it('should update all assets', async () => {
|
it('should update all assets', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
||||||
|
|
||||||
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true });
|
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true });
|
||||||
|
|
||||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true });
|
expect(mocks.asset.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not update Assets table if no relevant fields are provided', async () => {
|
it('should not update Assets table if no relevant fields are provided', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
|
|
||||||
await sut.updateAll(authStub.admin, {
|
await sut.updateAll(authStub.admin, {
|
||||||
ids: ['asset-1'],
|
ids: ['asset-1'],
|
||||||
latitude: 0,
|
latitude: 0,
|
||||||
@ -421,6 +449,7 @@ describe(AssetService.name, () => {
|
|||||||
|
|
||||||
it('should update Assets table if isArchived field is provided', async () => {
|
it('should update Assets table if isArchived field is provided', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
|
|
||||||
await sut.updateAll(authStub.admin, {
|
await sut.updateAll(authStub.admin, {
|
||||||
ids: ['asset-1'],
|
ids: ['asset-1'],
|
||||||
latitude: 0,
|
latitude: 0,
|
||||||
@ -624,25 +653,33 @@ describe(AssetService.name, () => {
|
|||||||
describe('run', () => {
|
describe('run', () => {
|
||||||
it('should run the refresh faces job', async () => {
|
it('should run the refresh faces job', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
|
|
||||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_FACES });
|
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_FACES });
|
||||||
|
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.FACE_DETECTION, data: { id: 'asset-1' } }]);
|
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.FACE_DETECTION, data: { id: 'asset-1' } }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run the refresh metadata job', async () => {
|
it('should run the refresh metadata job', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
|
|
||||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA });
|
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA });
|
||||||
|
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } }]);
|
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run the refresh thumbnails job', async () => {
|
it('should run the refresh thumbnails job', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
|
|
||||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL });
|
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL });
|
||||||
|
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1' } }]);
|
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.GENERATE_THUMBNAILS, data: { id: 'asset-1' } }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run the transcode video', async () => {
|
it('should run the transcode video', async () => {
|
||||||
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
mocks.access.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
|
|
||||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO });
|
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO });
|
||||||
|
|
||||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } }]);
|
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } }]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { MapService } from 'src/services/map.service';
|
|||||||
import { albumStub } from 'test/fixtures/album.stub';
|
import { albumStub } from 'test/fixtures/album.stub';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { partnerStub } from 'test/fixtures/partner.stub';
|
import { factory } from 'test/small.factory';
|
||||||
import { newTestService, ServiceMocks } from 'test/utils';
|
import { newTestService, ServiceMocks } from 'test/utils';
|
||||||
|
|
||||||
describe(MapService.name, () => {
|
describe(MapService.name, () => {
|
||||||
@ -34,6 +34,9 @@ describe(MapService.name, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should include partner assets', async () => {
|
it('should include partner assets', async () => {
|
||||||
|
const partner = factory.partner();
|
||||||
|
const auth = factory.auth({ id: partner.sharedWithId });
|
||||||
|
|
||||||
const asset = assetStub.withLocation;
|
const asset = assetStub.withLocation;
|
||||||
const marker = {
|
const marker = {
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
@ -43,13 +46,13 @@ describe(MapService.name, () => {
|
|||||||
state: asset.exifInfo!.state,
|
state: asset.exifInfo!.state,
|
||||||
country: asset.exifInfo!.country,
|
country: asset.exifInfo!.country,
|
||||||
};
|
};
|
||||||
mocks.partner.getAll.mockResolvedValue([partnerStub.adminToUser1]);
|
mocks.partner.getAll.mockResolvedValue([partner]);
|
||||||
mocks.map.getMapMarkers.mockResolvedValue([marker]);
|
mocks.map.getMapMarkers.mockResolvedValue([marker]);
|
||||||
|
|
||||||
const markers = await sut.getMapMarkers(authStub.user1, { withPartners: true });
|
const markers = await sut.getMapMarkers(auth, { withPartners: true });
|
||||||
|
|
||||||
expect(mocks.map.getMapMarkers).toHaveBeenCalledWith(
|
expect(mocks.map.getMapMarkers).toHaveBeenCalledWith(
|
||||||
[authStub.user1.user.id, partnerStub.adminToUser1.sharedById],
|
[auth.user.id, partner.sharedById],
|
||||||
expect.arrayContaining([]),
|
expect.arrayContaining([]),
|
||||||
{ withPartners: true },
|
{ withPartners: true },
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import { PartnerDirection } from 'src/repositories/partner.repository';
|
import { PartnerDirection } from 'src/repositories/partner.repository';
|
||||||
import { PartnerService } from 'src/services/partner.service';
|
import { PartnerService } from 'src/services/partner.service';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { factory } from 'test/small.factory';
|
||||||
import { partnerStub } from 'test/fixtures/partner.stub';
|
|
||||||
import { newTestService, ServiceMocks } from 'test/utils';
|
import { newTestService, ServiceMocks } from 'test/utils';
|
||||||
|
|
||||||
describe(PartnerService.name, () => {
|
describe(PartnerService.name, () => {
|
||||||
@ -19,35 +18,58 @@ describe(PartnerService.name, () => {
|
|||||||
|
|
||||||
describe('search', () => {
|
describe('search', () => {
|
||||||
it("should return a list of partners with whom I've shared my library", async () => {
|
it("should return a list of partners with whom I've shared my library", async () => {
|
||||||
mocks.partner.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]);
|
const user1 = factory.user();
|
||||||
await expect(sut.search(authStub.user1, { direction: PartnerDirection.SharedBy })).resolves.toBeDefined();
|
const user2 = factory.user();
|
||||||
expect(mocks.partner.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
|
const sharedWithUser2 = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||||
|
const sharedWithUser1 = factory.partner({ sharedBy: user2, sharedWith: user1 });
|
||||||
|
const auth = factory.auth({ id: user1.id });
|
||||||
|
|
||||||
|
mocks.partner.getAll.mockResolvedValue([sharedWithUser1, sharedWithUser2]);
|
||||||
|
|
||||||
|
await expect(sut.search(auth, { direction: PartnerDirection.SharedBy })).resolves.toBeDefined();
|
||||||
|
expect(mocks.partner.getAll).toHaveBeenCalledWith(user1.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of partners who have shared their libraries with me', async () => {
|
it('should return a list of partners who have shared their libraries with me', async () => {
|
||||||
mocks.partner.getAll.mockResolvedValue([partnerStub.adminToUser1, partnerStub.user1ToAdmin1]);
|
const user1 = factory.user();
|
||||||
await expect(sut.search(authStub.user1, { direction: PartnerDirection.SharedWith })).resolves.toBeDefined();
|
const user2 = factory.user();
|
||||||
expect(mocks.partner.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
|
const sharedWithUser2 = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||||
|
const sharedWithUser1 = factory.partner({ sharedBy: user2, sharedWith: user1 });
|
||||||
|
const auth = factory.auth({ id: user1.id });
|
||||||
|
|
||||||
|
mocks.partner.getAll.mockResolvedValue([sharedWithUser1, sharedWithUser2]);
|
||||||
|
await expect(sut.search(auth, { direction: PartnerDirection.SharedWith })).resolves.toBeDefined();
|
||||||
|
expect(mocks.partner.getAll).toHaveBeenCalledWith(user1.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
it('should create a new partner', async () => {
|
it('should create a new partner', async () => {
|
||||||
mocks.partner.get.mockResolvedValue(void 0);
|
const user1 = factory.user();
|
||||||
mocks.partner.create.mockResolvedValue(partnerStub.adminToUser1);
|
const user2 = factory.user();
|
||||||
|
const partner = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||||
|
const auth = factory.auth({ id: user1.id });
|
||||||
|
|
||||||
await expect(sut.create(authStub.admin, authStub.user1.user.id)).resolves.toBeDefined();
|
mocks.partner.get.mockResolvedValue(void 0);
|
||||||
|
mocks.partner.create.mockResolvedValue(partner);
|
||||||
|
|
||||||
|
await expect(sut.create(auth, user2.id)).resolves.toBeDefined();
|
||||||
|
|
||||||
expect(mocks.partner.create).toHaveBeenCalledWith({
|
expect(mocks.partner.create).toHaveBeenCalledWith({
|
||||||
sharedById: authStub.admin.user.id,
|
sharedById: partner.sharedById,
|
||||||
sharedWithId: authStub.user1.user.id,
|
sharedWithId: partner.sharedWithId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error when the partner already exists', async () => {
|
it('should throw an error when the partner already exists', async () => {
|
||||||
mocks.partner.get.mockResolvedValue(partnerStub.adminToUser1);
|
const user1 = factory.user();
|
||||||
|
const user2 = factory.user();
|
||||||
|
const partner = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||||
|
const auth = factory.auth({ id: user1.id });
|
||||||
|
|
||||||
await expect(sut.create(authStub.admin, authStub.user1.user.id)).rejects.toBeInstanceOf(BadRequestException);
|
mocks.partner.get.mockResolvedValue(partner);
|
||||||
|
|
||||||
|
await expect(sut.create(auth, user2.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.partner.create).not.toHaveBeenCalled();
|
expect(mocks.partner.create).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -55,17 +77,25 @@ describe(PartnerService.name, () => {
|
|||||||
|
|
||||||
describe('remove', () => {
|
describe('remove', () => {
|
||||||
it('should remove a partner', async () => {
|
it('should remove a partner', async () => {
|
||||||
mocks.partner.get.mockResolvedValue(partnerStub.adminToUser1);
|
const user1 = factory.user();
|
||||||
|
const user2 = factory.user();
|
||||||
|
const partner = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||||
|
const auth = factory.auth({ id: user1.id });
|
||||||
|
|
||||||
await sut.remove(authStub.admin, authStub.user1.user.id);
|
mocks.partner.get.mockResolvedValue(partner);
|
||||||
|
|
||||||
expect(mocks.partner.remove).toHaveBeenCalledWith(partnerStub.adminToUser1);
|
await sut.remove(auth, user2.id);
|
||||||
|
|
||||||
|
expect(mocks.partner.remove).toHaveBeenCalledWith({ sharedById: user1.id, sharedWithId: user2.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error when the partner does not exist', async () => {
|
it('should throw an error when the partner does not exist', async () => {
|
||||||
|
const user2 = factory.user();
|
||||||
|
const auth = factory.auth();
|
||||||
|
|
||||||
mocks.partner.get.mockResolvedValue(void 0);
|
mocks.partner.get.mockResolvedValue(void 0);
|
||||||
|
|
||||||
await expect(sut.remove(authStub.admin, authStub.user1.user.id)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.remove(auth, user2.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(mocks.partner.remove).not.toHaveBeenCalled();
|
expect(mocks.partner.remove).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -73,18 +103,24 @@ describe(PartnerService.name, () => {
|
|||||||
|
|
||||||
describe('update', () => {
|
describe('update', () => {
|
||||||
it('should require access', async () => {
|
it('should require access', async () => {
|
||||||
await expect(sut.update(authStub.admin, 'shared-by-id', { inTimeline: false })).rejects.toBeInstanceOf(
|
const user2 = factory.user();
|
||||||
BadRequestException,
|
const auth = factory.auth();
|
||||||
);
|
|
||||||
|
await expect(sut.update(auth, user2.id, { inTimeline: false })).rejects.toBeInstanceOf(BadRequestException);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update partner', async () => {
|
it('should update partner', async () => {
|
||||||
mocks.access.partner.checkUpdateAccess.mockResolvedValue(new Set(['shared-by-id']));
|
const user1 = factory.user();
|
||||||
mocks.partner.update.mockResolvedValue(partnerStub.adminToUser1);
|
const user2 = factory.user();
|
||||||
|
const partner = factory.partner({ sharedBy: user1, sharedWith: user2 });
|
||||||
|
const auth = factory.auth({ id: user1.id });
|
||||||
|
|
||||||
await expect(sut.update(authStub.admin, 'shared-by-id', { inTimeline: true })).resolves.toBeDefined();
|
mocks.access.partner.checkUpdateAccess.mockResolvedValue(new Set([user2.id]));
|
||||||
|
mocks.partner.update.mockResolvedValue(partner);
|
||||||
|
|
||||||
|
await expect(sut.update(auth, user2.id, { inTimeline: true })).resolves.toBeDefined();
|
||||||
expect(mocks.partner.update).toHaveBeenCalledWith(
|
expect(mocks.partner.update).toHaveBeenCalledWith(
|
||||||
{ sharedById: 'shared-by-id', sharedWithId: authStub.admin.user.id },
|
{ sharedById: user2.id, sharedWithId: user1.id },
|
||||||
{ inTimeline: true },
|
{ inTimeline: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
import { Partner } from 'src/database';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos/partner.dto';
|
import { PartnerResponseDto, PartnerSearchDto, UpdatePartnerDto } from 'src/dtos/partner.dto';
|
||||||
import { mapUser } from 'src/dtos/user.dto';
|
import { mapDatabaseUser } from 'src/dtos/user.dto';
|
||||||
import { PartnerEntity } from 'src/entities/partner.entity';
|
|
||||||
import { Permission } from 'src/enum';
|
import { Permission } from 'src/enum';
|
||||||
import { PartnerDirection, PartnerIds } from 'src/repositories/partner.repository';
|
import { PartnerDirection, PartnerIds } from 'src/repositories/partner.repository';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
@ -27,14 +27,14 @@ export class PartnerService extends BaseService {
|
|||||||
throw new BadRequestException('Partner not found');
|
throw new BadRequestException('Partner not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.partnerRepository.remove(partner);
|
await this.partnerRepository.remove(partnerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(auth: AuthDto, { direction }: PartnerSearchDto): Promise<PartnerResponseDto[]> {
|
async search(auth: AuthDto, { direction }: PartnerSearchDto): Promise<PartnerResponseDto[]> {
|
||||||
const partners = await this.partnerRepository.getAll(auth.user.id);
|
const partners = await this.partnerRepository.getAll(auth.user.id);
|
||||||
const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId';
|
const key = direction === PartnerDirection.SharedBy ? 'sharedById' : 'sharedWithId';
|
||||||
return partners
|
return partners
|
||||||
.filter((partner) => partner.sharedBy && partner.sharedWith) // Filter out soft deleted users
|
.filter((partner): partner is Partner => !!(partner.sharedBy && partner.sharedWith)) // Filter out soft deleted users
|
||||||
.filter((partner) => partner[key] === auth.user.id)
|
.filter((partner) => partner[key] === auth.user.id)
|
||||||
.map((partner) => this.mapPartner(partner, direction));
|
.map((partner) => this.mapPartner(partner, direction));
|
||||||
}
|
}
|
||||||
@ -47,14 +47,12 @@ export class PartnerService extends BaseService {
|
|||||||
return this.mapPartner(entity, PartnerDirection.SharedWith);
|
return this.mapPartner(entity, PartnerDirection.SharedWith);
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapPartner(partner: PartnerEntity, direction: PartnerDirection): PartnerResponseDto {
|
private mapPartner(partner: Partner, direction: PartnerDirection): PartnerResponseDto {
|
||||||
// this is opposite to return the non-me user of the "partner"
|
// this is opposite to return the non-me user of the "partner"
|
||||||
const user = mapUser(
|
const user = mapDatabaseUser(
|
||||||
direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy,
|
direction === PartnerDirection.SharedBy ? partner.sharedWith : partner.sharedBy,
|
||||||
) as PartnerResponseDto;
|
) as PartnerResponseDto;
|
||||||
|
|
||||||
user.inTimeline = partner.inTimeline;
|
return { ...user, inTimeline: partner.inTimeline };
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { AssetEntity } from 'src/entities/asset.entity';
|
|||||||
import { SyncService } from 'src/services/sync.service';
|
import { SyncService } from 'src/services/sync.service';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { partnerStub } from 'test/fixtures/partner.stub';
|
import { factory } from 'test/small.factory';
|
||||||
import { newTestService, ServiceMocks } from 'test/utils';
|
import { newTestService, ServiceMocks } from 'test/utils';
|
||||||
|
|
||||||
const untilDate = new Date(2024);
|
const untilDate = new Date(2024);
|
||||||
@ -38,10 +38,15 @@ describe(SyncService.name, () => {
|
|||||||
|
|
||||||
describe('getChangesForDeltaSync', () => {
|
describe('getChangesForDeltaSync', () => {
|
||||||
it('should return a response requiring a full sync when partners are out of sync', async () => {
|
it('should return a response requiring a full sync when partners are out of sync', async () => {
|
||||||
mocks.partner.getAll.mockResolvedValue([partnerStub.adminToUser1]);
|
const partner = factory.partner();
|
||||||
|
const auth = factory.auth({ id: partner.sharedWithId });
|
||||||
|
|
||||||
|
mocks.partner.getAll.mockResolvedValue([partner]);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [authStub.user1.user.id] }),
|
sut.getDeltaSync(authStub.user1, { updatedAfter: new Date(), userIds: [auth.user.id] }),
|
||||||
).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] });
|
).resolves.toEqual({ needsFullSync: true, upserted: [], deleted: [] });
|
||||||
|
|
||||||
expect(mocks.asset.getChangedDeltaSync).toHaveBeenCalledTimes(0);
|
expect(mocks.asset.getChangedDeltaSync).toHaveBeenCalledTimes(0);
|
||||||
expect(mocks.audit.getAfter).toHaveBeenCalledTimes(0);
|
expect(mocks.audit.getAfter).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
23
server/test/fixtures/partner.stub.ts
vendored
23
server/test/fixtures/partner.stub.ts
vendored
@ -1,23 +0,0 @@
|
|||||||
import { PartnerEntity } from 'src/entities/partner.entity';
|
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
|
||||||
|
|
||||||
export const partnerStub = {
|
|
||||||
adminToUser1: Object.freeze<PartnerEntity>({
|
|
||||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
|
||||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
|
||||||
sharedById: userStub.admin.id,
|
|
||||||
sharedBy: userStub.admin,
|
|
||||||
sharedWith: userStub.user1,
|
|
||||||
sharedWithId: userStub.user1.id,
|
|
||||||
inTimeline: true,
|
|
||||||
}),
|
|
||||||
user1ToAdmin1: Object.freeze<PartnerEntity>({
|
|
||||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
|
||||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
|
||||||
sharedBy: userStub.user1,
|
|
||||||
sharedById: userStub.user1.id,
|
|
||||||
sharedWithId: userStub.admin.id,
|
|
||||||
sharedWith: userStub.admin,
|
|
||||||
inTimeline: true,
|
|
||||||
}),
|
|
||||||
};
|
|
@ -1,5 +1,5 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { ApiKey, Asset, AuthApiKey, AuthUser, Library, User } from 'src/database';
|
import { ApiKey, Asset, AuthApiKey, AuthUser, Library, Partner, User } from 'src/database';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { OnThisDayData } from 'src/entities/memory.entity';
|
import { OnThisDayData } from 'src/entities/memory.entity';
|
||||||
import { AssetStatus, AssetType, MemoryType, Permission } from 'src/enum';
|
import { AssetStatus, AssetType, MemoryType, Permission } from 'src/enum';
|
||||||
@ -42,6 +42,23 @@ const authUserFactory = (authUser: Partial<AuthUser> = {}) => ({
|
|||||||
...authUser,
|
...authUser,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const partnerFactory = (partner: Partial<Partner> = {}) => {
|
||||||
|
const sharedBy = userFactory(partner.sharedBy || {});
|
||||||
|
const sharedWith = userFactory(partner.sharedWith || {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
sharedById: sharedBy.id,
|
||||||
|
sharedBy,
|
||||||
|
sharedWithId: sharedWith.id,
|
||||||
|
sharedWith,
|
||||||
|
createdAt: newDate(),
|
||||||
|
updatedAt: newDate(),
|
||||||
|
updateId: newUpdateId(),
|
||||||
|
inTimeline: true,
|
||||||
|
...partner,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const sessionFactory = () => ({
|
const sessionFactory = () => ({
|
||||||
id: newUuid(),
|
id: newUuid(),
|
||||||
createdAt: newDate(),
|
createdAt: newDate(),
|
||||||
@ -177,6 +194,7 @@ export const factory = {
|
|||||||
authUser: authUserFactory,
|
authUser: authUserFactory,
|
||||||
library: libraryFactory,
|
library: libraryFactory,
|
||||||
memory: memoryFactory,
|
memory: memoryFactory,
|
||||||
|
partner: partnerFactory,
|
||||||
session: sessionFactory,
|
session: sessionFactory,
|
||||||
stack: stackFactory,
|
stack: stackFactory,
|
||||||
user: userFactory,
|
user: userFactory,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user