diff --git a/backend/src/history/history.service.spec.ts b/backend/src/history/history.service.spec.ts index 33f77f9ba..d2a64d228 100644 --- a/backend/src/history/history.service.spec.ts +++ b/backend/src/history/history.service.spec.ts @@ -9,7 +9,13 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm'; import assert from 'assert'; import { Mock } from 'ts-mockery'; -import { DataSource, EntityManager, Repository } from 'typeorm'; +import { + DataSource, + EntityManager, + EntityTarget, + Repository, + SelectQueryBuilder, +} from 'typeorm'; import { AuthToken } from '../auth/auth-token.entity'; import { Author } from '../authors/author.entity'; @@ -38,6 +44,8 @@ import { HistoryEntryImportDto } from './history-entry-import.dto'; import { HistoryEntry } from './history-entry.entity'; import { HistoryService } from './history.service'; +import Any = jasmine.Any; + describe('HistoryService', () => { let service: HistoryService; let historyRepo: Repository; @@ -47,16 +55,17 @@ describe('HistoryService', () => { [(entityManager: EntityManager) => Promise] >; - class CreateQueryBuilderClass { - leftJoinAndSelect: () => CreateQueryBuilderClass; - where: () => CreateQueryBuilderClass; - orWhere: () => CreateQueryBuilderClass; - setParameter: () => CreateQueryBuilderClass; - getOne: () => HistoryEntry; - getMany: () => HistoryEntry[]; + class CreateQueryBuilderClass { + leftJoinAndSelect: () => CreateQueryBuilderClass; + where: () => CreateQueryBuilderClass; + orWhere: () => CreateQueryBuilderClass; + setParameter: () => CreateQueryBuilderClass; + getOne: () => Entity; + getMany: () => Entity[]; } - let createQueryBuilderFunc: CreateQueryBuilderClass; + let createQueryBuilderFunc: CreateQueryBuilderClass; + let createQueryBuilderFuncNote: CreateQueryBuilderClass; beforeEach(async () => { noteRepo = new Repository( @@ -148,12 +157,29 @@ describe('HistoryService', () => { getOne: () => historyEntry, getMany: () => [historyEntry], }; - createQueryBuilderFunc = createQueryBuilder as CreateQueryBuilderClass; + createQueryBuilderFunc = + createQueryBuilder as CreateQueryBuilderClass; + const note = Note.create(null); jest .spyOn(historyRepo, 'createQueryBuilder') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementation(() => createQueryBuilder); + const createQueryBuilderNote = { + leftJoinAndSelect: () => createQueryBuilderNote, + where: () => createQueryBuilderNote, + orWhere: () => createQueryBuilderNote, + setParameter: () => createQueryBuilderNote, + getOne: () => note, + getMany: () => [note], + }; + createQueryBuilderFuncNote = + createQueryBuilderNote as CreateQueryBuilderClass; + jest + .spyOn(historyRepo, 'createQueryBuilder') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + .mockImplementation(() => createQueryBuilderNote); }); it('should be defined', () => { @@ -388,16 +414,41 @@ describe('HistoryService', () => { updatedAt: historyEntryImport.lastVisitedAt, }; - const createQueryBuilder = mockSelectQueryBuilderInRepo(noteRepo, note); + function createQueryBuilder(entityClass: any): SelectQueryBuilder { + if (entityClass.name == 'HistoryEntry') { + return mockSelectQueryBuilderInRepo( + historyRepo, + newlyCreatedHistoryEntry, + ); + } else { + return mockSelectQueryBuilderInRepo(noteRepo, note); + } + } + /* + class QueryBuilderExtension extends SelectQueryBuilder{ + createQueryBuilder(): this { + if (Entity.toString() == 'HistoryEntry') { + return mockSelectQueryBuilderInRepo( + historyRepo, + newlyCreatedHistoryEntry, + ); + } else { + return mockSelectQueryBuilderInRepo(noteRepo, note); + } + } + } + */ + const mockedManager = Mock.of({ find: jest.fn().mockResolvedValueOnce([historyEntry]), - createQueryBuilder: () => createQueryBuilder, - remove: jest.fn().mockImplementationOnce(async (_: HistoryEntry) => { - // TODO: reimplement checks below - //expect(await (await entry.note).aliases).toHaveLength(1); - //expect((await (await entry.note).aliases)[0].name).toEqual(alias); - //expect(entry.pinStatus).toEqual(false); - }), + remove: jest + .fn() + .mockImplementationOnce(async (entry: HistoryEntry) => { + expect(await (await entry.note).aliases).toHaveLength(1); + expect((await (await entry.note).aliases)[0].name).toEqual(alias); + expect(entry.pinStatus).toEqual(true); + }), + createQueryBuilder: jest.fn(), save: jest.fn().mockImplementationOnce(async (entry: HistoryEntry) => { expect((await entry.note).aliases).toEqual( (await newlyCreatedHistoryEntry.note).aliases, @@ -406,6 +457,11 @@ describe('HistoryService', () => { expect(entry.updatedAt).toEqual(newlyCreatedHistoryEntry.updatedAt); }), }); + jest + .spyOn(mockedManager, 'createQueryBuilder') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + .mockImplementation((type) => createQueryBuilder(type)); mockedTransaction.mockImplementation((cb) => cb(mockedManager)); await service.setHistory(user, [historyEntryImport]); }); diff --git a/backend/src/utils/test-utils/mockRepository.ts b/backend/src/utils/test-utils/mockRepository.ts new file mode 100644 index 000000000..1d8ea226c --- /dev/null +++ b/backend/src/utils/test-utils/mockRepository.ts @@ -0,0 +1,250 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +/* +import { + DeepPartial, + DeleteResult, + EntityManager, + EntityTarget, + FindManyOptions, + FindOptionsWhere, + InsertResult, + ObjectID, + QueryRunner, + RemoveOptions, + Repository, + SaveOptions, + SelectQueryBuilder, + UpdateResult, +} from 'typeorm'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { UpsertOptions } from 'typeorm/repository/UpsertOptions'; + +class MockRepository implements Repository { + + entities: T[] + + createQueryBuilder( + alias?: string, + queryRunner?: QueryRunner, + ): SelectQueryBuilder { + new SelectQueryBuilder() + //return super.createQueryBuilder(alias, queryRunner); + } + find(options?: FindManyOptions): Promise { + //return super.find(options); + } + + clear(): Promise { + return Promise.resolve(undefined); + } + + count(options: FindManyOptions | undefined): Promise { + return Promise.resolve(0); + } + + countBy(where: FindOptionsWhere | FindOptionsWhere[]): Promise { + return Promise.resolve(0); + } + + create(): T { + return undefined; + } + + decrement( + conditions: FindOptionsWhere, + propertyPath: string, + value: number | string, + ): Promise { + return Promise.resolve(undefined); + } + + delete( + criteria: + | string + | string[] + | number + | number[] + | Date + | Date[] + | ObjectID + | ObjectID[] + | FindOptionsWhere, + ): Promise { + return Promise.resolve(undefined); + } + + extend( + custom: CustomRepository & ThisType & CustomRepository>, + ): Repository & CustomRepository { + return undefined; + } + + findAndCount( + options: FindManyOptions | undefined, + ): Promise<[T[], number]> { + return Promise.resolve([[], 0]); + } + + findAndCountBy( + where: FindOptionsWhere | FindOptionsWhere[], + ): Promise<[T[], number]> { + return Promise.resolve([[], 0]); + } + + findBy(where: FindOptionsWhere | FindOptionsWhere[]): Promise { + return Promise.resolve([]); + } + + findByIds(ids: any[]): Promise { + return Promise.resolve([]); + } + + findOne(options: FindOneOptions): Promise { + return Promise.resolve(undefined); + } + + findOneBy( + where: FindOptionsWhere | FindOptionsWhere[], + ): Promise { + return Promise.resolve(undefined); + } + + findOneById(id: number | string | Date | ObjectID): Promise { + return Promise.resolve(undefined); + } + + findOneByOrFail( + where: FindOptionsWhere | FindOptionsWhere[], + ): Promise { + return Promise.resolve(undefined); + } + + findOneOrFail(options: FindOneOptions): Promise { + return Promise.resolve(undefined); + } + + getId(entity: T): any {} + + hasId(entity: T): boolean { + return false; + } + + increment( + conditions: FindOptionsWhere, + propertyPath: string, + value: number | string, + ): Promise { + return Promise.resolve(undefined); + } + + insert( + entity: QueryDeepPartialEntity | QueryDeepPartialEntity[], + ): Promise { + return Promise.resolve(undefined); + } + + readonly manager: EntityManager; + + merge(mergeIntoEntity: T, entityLikes: DeepPartial): T { + return undefined; + } + + get metadata(): import('..').EntityMetadata { + return undefined; + } + + preload(entityLike: DeepPartial): Promise { + return Promise.resolve(undefined); + } + + query(query: string, parameters: any[] | undefined): Promise { + return Promise.resolve(undefined); + } + + readonly queryRunner: QueryRunner; + + recover( + entities: T[], + options: SaveOptions & { reload: false }, + ): Promise { + return Promise.resolve([]); + } + + remove(entities: T[], options: RemoveOptions | undefined): Promise { + return Promise.resolve([]); + } + + restore( + criteria: + | string + | string[] + | number + | number[] + | Date + | Date[] + | ObjectID + | ObjectID[] + | FindOptionsWhere, + ): Promise { + return Promise.resolve(undefined); + } + + save( + entities: T[], + options: SaveOptions & { reload: false }, + ): Promise { + return Promise.resolve([]); + } + + softDelete( + criteria: + | string + | string[] + | number + | number[] + | Date + | Date[] + | ObjectID + | ObjectID[] + | FindOptionsWhere, + ): Promise { + return Promise.resolve(undefined); + } + + softRemove( + entities: T[], + options: SaveOptions & { reload: false }, + ): Promise { + return Promise.resolve([]); + } + + readonly target: EntityTarget; + + update( + criteria: + | string + | string[] + | number + | number[] + | Date + | Date[] + | ObjectID + | ObjectID[] + | FindOptionsWhere, + partialEntity: QueryDeepPartialEntity, + ): Promise { + return Promise.resolve(undefined); + } + + upsert( + entityOrEntities: QueryDeepPartialEntity | QueryDeepPartialEntity[], + conflictPathsOrOptions: string[] | UpsertOptions, + ): Promise { + return Promise.resolve(undefined); + } +} +*/