diff --git a/src/api/public/notes/notes.controller.spec.ts b/src/api/public/notes/notes.controller.spec.ts index 13ae993bc..6df9c73cb 100644 --- a/src/api/public/notes/notes.controller.spec.ts +++ b/src/api/public/notes/notes.controller.spec.ts @@ -19,6 +19,8 @@ import { Identity } from '../../../users/identity.entity'; import { User } from '../../../users/user.entity'; import { UsersModule } from '../../../users/users.module'; import { NotesController } from './notes.controller'; +import { HistoryModule } from '../../../history/history.module'; +import { HistoryEntry } from '../../../history/history-entry.entity'; describe('Notes Controller', () => { let controller: NotesController; @@ -37,7 +39,7 @@ describe('Notes Controller', () => { useValue: {}, }, ], - imports: [RevisionsModule, UsersModule, LoggerModule], + imports: [RevisionsModule, UsersModule, LoggerModule, HistoryModule], }) .overrideProvider(getRepositoryToken(Note)) .useValue({}) @@ -57,6 +59,8 @@ describe('Notes Controller', () => { .useValue({}) .overrideProvider(getRepositoryToken(Tag)) .useValue({}) + .overrideProvider(getRepositoryToken(HistoryEntry)) + .useValue({}) .compile(); controller = module.get(NotesController); diff --git a/src/history/history.service.spec.ts b/src/history/history.service.spec.ts index ba6ba2b6e..ccf0ba04e 100644 --- a/src/history/history.service.spec.ts +++ b/src/history/history.service.spec.ts @@ -7,20 +7,267 @@ import { Test, TestingModule } from '@nestjs/testing'; import { LoggerModule } from '../logger/logger.module'; import { HistoryService } from './history.service'; +import { UsersModule } from '../users/users.module'; +import { NotesModule } from '../notes/notes.module'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Identity } from '../users/identity.entity'; +import { User } from '../users/user.entity'; +import { AuthorColor } from '../notes/author-color.entity'; +import { Authorship } from '../revisions/authorship.entity'; +import { HistoryEntry } from './history-entry.entity'; +import { Note } from '../notes/note.entity'; +import { Tag } from '../notes/tag.entity'; +import { AuthToken } from '../auth/auth-token.entity'; +import { Revision } from '../revisions/revision.entity'; +import { Repository } from 'typeorm'; +import { NotInDBError } from '../errors/errors'; describe('HistoryService', () => { let service: HistoryService; + let historyRepo: Repository; + let noteRepo: Repository; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - providers: [HistoryService], - imports: [LoggerModule], - }).compile(); + providers: [ + HistoryService, + { + provide: getRepositoryToken(HistoryEntry), + useClass: Repository, + }, + ], + imports: [LoggerModule, UsersModule, NotesModule], + }) + .overrideProvider(getRepositoryToken(User)) + .useValue({}) + .overrideProvider(getRepositoryToken(AuthToken)) + .useValue({}) + .overrideProvider(getRepositoryToken(Identity)) + .useValue({}) + .overrideProvider(getRepositoryToken(Authorship)) + .useValue({}) + .overrideProvider(getRepositoryToken(AuthorColor)) + .useValue({}) + .overrideProvider(getRepositoryToken(Revision)) + .useValue({}) + .overrideProvider(getRepositoryToken(Note)) + .useClass(Repository) + .overrideProvider(getRepositoryToken(Tag)) + .useValue({}) + .compile(); service = module.get(HistoryService); + historyRepo = module.get>( + getRepositoryToken(HistoryEntry), + ); + noteRepo = module.get>(getRepositoryToken(Note)); }); it('should be defined', () => { expect(service).toBeDefined(); }); + + describe('getEntriesByUser', () => { + describe('works', () => { + it('with an empty list', async () => { + jest.spyOn(historyRepo, 'find').mockResolvedValueOnce([]); + expect(await service.getEntriesByUser({} as User)).toEqual([]); + }); + + it('with an one element list', async () => { + const historyEntry = new HistoryEntry(); + jest.spyOn(historyRepo, 'find').mockResolvedValueOnce([historyEntry]); + expect(await service.getEntriesByUser({} as User)).toEqual([ + historyEntry, + ]); + }); + + it('with an multiple element list', async () => { + const historyEntry = new HistoryEntry(); + const historyEntry2 = new HistoryEntry(); + jest + .spyOn(historyRepo, 'find') + .mockResolvedValueOnce([historyEntry, historyEntry2]); + expect(await service.getEntriesByUser({} as User)).toEqual([ + historyEntry, + historyEntry2, + ]); + }); + }); + }); + + describe('createOrUpdateHistoryEntry', () => { + describe('works', () => { + it('without an preexisting entry', async () => { + const user = new User(); + const alias = 'alias'; + jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined); + jest + .spyOn(historyRepo, 'save') + .mockImplementation( + async (entry: HistoryEntry): Promise => entry, + ); + const createHistoryEntry = await service.createOrUpdateHistoryEntry( + Note.create(user, alias), + user, + ); + expect(createHistoryEntry.note.alias).toEqual(alias); + expect(createHistoryEntry.note.owner).toEqual(user); + expect(createHistoryEntry.user).toEqual(user); + expect(createHistoryEntry.pinStatus).toEqual(false); + }); + + it('with an preexisting entry', async () => { + const user = new User(); + const alias = 'alias'; + const historyEntry = HistoryEntry.create( + user, + Note.create(user, alias), + ); + jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry); + jest + .spyOn(historyRepo, 'save') + .mockImplementation( + async (entry: HistoryEntry): Promise => entry, + ); + const createHistoryEntry = await service.createOrUpdateHistoryEntry( + Note.create(user, alias), + user, + ); + expect(createHistoryEntry.note.alias).toEqual(alias); + expect(createHistoryEntry.note.owner).toEqual(user); + expect(createHistoryEntry.user).toEqual(user); + expect(createHistoryEntry.pinStatus).toEqual(false); + expect(createHistoryEntry.updatedAt.getTime()).toBeGreaterThanOrEqual( + historyEntry.updatedAt.getTime(), + ); + }); + }); + }); + + describe('updateHistoryEntry', () => { + describe('works', () => { + it('with an entry', async () => { + const user = new User(); + const alias = 'alias'; + const note = Note.create(user, alias); + const historyEntry = HistoryEntry.create(user, note); + jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry); + jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note); + jest + .spyOn(historyRepo, 'save') + .mockImplementation( + async (entry: HistoryEntry): Promise => entry, + ); + const updatedHistoryEntry = await service.updateHistoryEntry( + alias, + user, + { + pinStatus: true, + }, + ); + expect(updatedHistoryEntry.note.alias).toEqual(alias); + expect(updatedHistoryEntry.note.owner).toEqual(user); + expect(updatedHistoryEntry.user).toEqual(user); + expect(updatedHistoryEntry.pinStatus).toEqual(true); + }); + + it('without an entry', async () => { + const user = new User(); + const alias = 'alias'; + const note = Note.create(user, alias); + jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined); + jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note); + try { + await service.updateHistoryEntry(alias, user, { + pinStatus: true, + }); + } catch (e) { + expect(e).toBeInstanceOf(NotInDBError); + } + }); + }); + }); + + describe('deleteHistoryEntry', () => { + describe('works', () => { + it('with an entry', async () => { + const user = new User(); + const alias = 'alias'; + const note = Note.create(user, alias); + const historyEntry = HistoryEntry.create(user, note); + jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(historyEntry); + jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note); + jest.spyOn(historyRepo, 'remove').mockImplementation( + async (entry: HistoryEntry): Promise => { + expect(entry).toEqual(historyEntry); + return entry; + }, + ); + await service.deleteHistoryEntry(alias, user); + }); + + it('without an entry', async () => { + const user = new User(); + const alias = 'alias'; + const note = Note.create(user, alias); + jest.spyOn(historyRepo, 'findOne').mockResolvedValueOnce(undefined); + jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note); + try { + await service.deleteHistoryEntry(alias, user); + } catch (e) { + expect(e).toBeInstanceOf(NotInDBError); + } + }); + }); + }); + + describe('toHistoryEntryDto', () => { + describe('works', () => { + it('with aliased note', async () => { + const user = new User(); + const alias = 'alias'; + const title = 'title'; + const tags = ['tag1', 'tag2']; + const note = Note.create(user, alias); + note.title = title; + note.tags = tags.map((tag) => { + const newTag = new Tag(); + newTag.name = tag; + return newTag; + }); + const historyEntry = HistoryEntry.create(user, note); + historyEntry.pinStatus = true; + jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note); + const historyEntryDto = await service.toHistoryEntryDto(historyEntry); + expect(historyEntryDto.pinStatus).toEqual(true); + expect(historyEntryDto.identifier).toEqual(alias); + expect(historyEntryDto.tags).toEqual(tags); + expect(historyEntryDto.title).toEqual(title); + }); + + it('with regular note', async () => { + const user = new User(); + const title = 'title'; + const id = 'id'; + const tags = ['tag1', 'tag2']; + const note = Note.create(user); + note.title = title; + note.id = id; + note.tags = tags.map((tag) => { + const newTag = new Tag(); + newTag.name = tag; + return newTag; + }); + const historyEntry = HistoryEntry.create(user, note); + historyEntry.pinStatus = true; + jest.spyOn(noteRepo, 'findOne').mockResolvedValueOnce(note); + const historyEntryDto = await service.toHistoryEntryDto(historyEntry); + expect(historyEntryDto.pinStatus).toEqual(true); + expect(historyEntryDto.identifier).toEqual(id); + expect(historyEntryDto.tags).toEqual(tags); + expect(historyEntryDto.title).toEqual(title); + }); + }); + }); }); diff --git a/src/history/history.service.ts b/src/history/history.service.ts index 16343b16d..84583220e 100644 --- a/src/history/history.service.ts +++ b/src/history/history.service.ts @@ -10,7 +10,7 @@ import { HistoryEntryUpdateDto } from './history-entry-update.dto'; import { HistoryEntryDto } from './history-entry.dto'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { HistoryEntry } from './history-entry.enity'; +import { HistoryEntry } from './history-entry.entity'; import { UsersService } from '../users/users.service'; import { NotesService } from '../notes/notes.service'; import { User } from '../users/user.entity'; diff --git a/test/public-api/users.e2e-spec.ts b/test/public-api/users.e2e-spec.ts index 4bcea119c..4c406ccb4 100644 --- a/test/public-api/users.e2e-spec.ts +++ b/test/public-api/users.e2e-spec.ts @@ -7,28 +7,68 @@ import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import * as request from 'supertest'; -import { AppModule } from '../../src/app.module'; import { UserInfoDto } from '../../src/users/user-info.dto'; import { HistoryService } from '../../src/history/history.service'; import { NotesService } from '../../src/notes/notes.service'; import { HistoryEntryUpdateDto } from '../../src/history/history-entry-update.dto'; import { HistoryEntryDto } from '../../src/history/history-entry.dto'; +import { HistoryEntry } from '../../src/history/history-entry.entity'; +import { UsersService } from '../../src/users/users.service'; +import { TokenAuthGuard } from '../../src/auth/token-auth.guard'; +import { MockAuthGuard } from '../../src/auth/mock-auth.guard'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { PublicApiModule } from '../../src/api/public/public-api.module'; +import { NotesModule } from '../../src/notes/notes.module'; +import { PermissionsModule } from '../../src/permissions/permissions.module'; +import { GroupsModule } from '../../src/groups/groups.module'; +import { LoggerModule } from '../../src/logger/logger.module'; +import { AuthModule } from '../../src/auth/auth.module'; +import { UsersModule } from '../../src/users/users.module'; +import { HistoryModule } from '../../src/history/history.module'; +import { ConfigModule } from '@nestjs/config'; +import mediaConfigMock from '../../src/config/media.config.mock'; +import { User } from '../../src/users/user.entity'; // TODO Tests have to be reworked using UserService functions describe('Notes', () => { let app: INestApplication; - //let usersService: UsersService; let historyService: HistoryService; let notesService: NotesService; + let user: User; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - // TODO Create User and generateAPI Token or other Auth + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [mediaConfigMock], + }), + PublicApiModule, + NotesModule, + PermissionsModule, + GroupsModule, + TypeOrmModule.forRoot({ + type: 'sqlite', + database: './hedgedoc-e2e-me.sqlite', + autoLoadEntities: true, + synchronize: true, + dropSchema: true, + }), + LoggerModule, + AuthModule, + UsersModule, + HistoryModule, + ], + }) + .overrideGuard(TokenAuthGuard) + .useClass(MockAuthGuard) + .compile(); app = moduleRef.createNestApplication(); - //usersService = moduleRef.get(UsersService); + notesService = moduleRef.get(NotesService); + historyService = moduleRef.get(HistoryService); + const userService = moduleRef.get(UsersService); + user = await userService.createUser('hardcoded', 'Testy'); await app.init(); }); @@ -42,87 +82,65 @@ describe('Notes', () => { expect(response.body.content).toEqual(userInfo); }); - it.skip(`GET /me/history`, async () => { - // TODO user has to be chosen - /* TODO Note maybe not added to history by createNote, - use function from HistoryService instead - */ - await notesService.createNote('', 'testGetHistory'); + it(`GET /me/history`, async () => { + const noteName = 'testGetNoteHistory1'; + const note = await notesService.createNote('', noteName); + const createdHistoryEntry = await historyService.createOrUpdateHistoryEntry( + note, + user, + ); const response = await request(app.getHttpServer()) .get('/me/history') .expect('Content-Type', /json/) .expect(200); - let historyEntry: HistoryEntryDto; - for (const e of response.body.content) { - if ((e).metadata.alias === 'testGetHistory') { - historyEntry = e; + const history = response.body; + for (const historyEntry of history) { + if ((historyEntry).identifier === 'testGetHistory') { + expect(historyEntry).toEqual(createdHistoryEntry); } } - expect(historyEntry).toEqual(history); }); - it.skip(`GET /me/history/{note}`, async () => { - const noteName = 'testGetNoteHistory'; - /* TODO Note maybe not added to history by createNote, - use function from HistoryService instead - */ - await notesService.createNote('', noteName); - const response = await request(app.getHttpServer()) - .get('/me/history/' + noteName) - .expect('Content-Type', /json/) - .expect(200); - expect(response.body.metadata?.id).toBeDefined(); - return expect(response.body.metadata.alias).toEqual(noteName); - }); - - it.skip(`DELETE /me/history/{note}`, async () => { - const noteName = 'testDeleteNoteHistory'; - /* TODO Note maybe not added to history by createNote, - use function from HistoryService instead - */ - await notesService.createNote('This is a test note.', noteName); - const response = await request(app.getHttpServer()) - .delete('/me/history/test3') - .expect(204); - expect(response.body.content).toBeNull(); - const history = historyService.getEntriesByUser('testuser'); - let historyEntry: HistoryEntryDto = null; - for (const e of history) { - if (e.metadata.alias === noteName) { - historyEntry = e; - } - } - return expect(historyEntry).toBeNull(); - }); - - it.skip(`PUT /me/history/{note}`, async () => { - const noteName = 'testPutNoteHistory'; - // TODO use function from HistoryService to add an History Entry - await notesService.createNote('', noteName); + it(`PUT /me/history/{note}`, async () => { + const noteName = 'testGetNoteHistory2'; + const note = await notesService.createNote('', noteName); + await historyService.createOrUpdateHistoryEntry(note, user); const historyEntryUpdateDto = new HistoryEntryUpdateDto(); historyEntryUpdateDto.pinStatus = true; const response = await request(app.getHttpServer()) .put('/me/history/' + noteName) .send(historyEntryUpdateDto) .expect(200); - // TODO parameter is not used for now - const history = historyService.getEntriesByUser('testuser'); - let historyEntry: HistoryEntryDto; - for (const e of response.body.content) { - if ((e).metadata.alias === noteName) { - historyEntry = e; - } - } + const history = await historyService.getEntriesByUser(user); + let historyEntry: HistoryEntryDto = response.body; expect(historyEntry.pinStatus).toEqual(true); historyEntry = null; for (const e of history) { - if (e.metadata.alias === noteName) { - historyEntry = e; + if (e.note.alias === noteName) { + historyEntry = await historyService.toHistoryEntryDto(e); } } expect(historyEntry.pinStatus).toEqual(true); }); + it(`DELETE /me/history/{note}`, async () => { + const noteName = 'testGetNoteHistory3'; + const note = await notesService.createNote('', noteName); + await historyService.createOrUpdateHistoryEntry(note, user); + const response = await request(app.getHttpServer()) + .delete(`/me/history/${noteName}`) + .expect(204); + expect(response.body).toEqual({}); + const history = await historyService.getEntriesByUser(user); + let historyEntry: HistoryEntry = null; + for (const e of history) { + if ((e).note.alias === noteName) { + historyEntry = e; + } + } + return expect(historyEntry).toBeNull(); + }); + it.skip(`GET /me/notes/`, async () => { // TODO use function from HistoryService to add an History Entry await notesService.createNote('This is a test note.', 'test7');