History: Add unit and e2e test

Add unit tests for history service
Adapt relevant me e2e tests to work

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2021-02-03 21:46:36 +01:00
parent 7f1afc30c9
commit 10ef4fcee1
4 changed files with 339 additions and 70 deletions

View file

@ -19,6 +19,8 @@ import { Identity } from '../../../users/identity.entity';
import { User } from '../../../users/user.entity'; import { User } from '../../../users/user.entity';
import { UsersModule } from '../../../users/users.module'; import { UsersModule } from '../../../users/users.module';
import { NotesController } from './notes.controller'; import { NotesController } from './notes.controller';
import { HistoryModule } from '../../../history/history.module';
import { HistoryEntry } from '../../../history/history-entry.entity';
describe('Notes Controller', () => { describe('Notes Controller', () => {
let controller: NotesController; let controller: NotesController;
@ -37,7 +39,7 @@ describe('Notes Controller', () => {
useValue: {}, useValue: {},
}, },
], ],
imports: [RevisionsModule, UsersModule, LoggerModule], imports: [RevisionsModule, UsersModule, LoggerModule, HistoryModule],
}) })
.overrideProvider(getRepositoryToken(Note)) .overrideProvider(getRepositoryToken(Note))
.useValue({}) .useValue({})
@ -57,6 +59,8 @@ describe('Notes Controller', () => {
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(Tag)) .overrideProvider(getRepositoryToken(Tag))
.useValue({}) .useValue({})
.overrideProvider(getRepositoryToken(HistoryEntry))
.useValue({})
.compile(); .compile();
controller = module.get<NotesController>(NotesController); controller = module.get<NotesController>(NotesController);

View file

@ -7,20 +7,267 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { LoggerModule } from '../logger/logger.module'; import { LoggerModule } from '../logger/logger.module';
import { HistoryService } from './history.service'; 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', () => { describe('HistoryService', () => {
let service: HistoryService; let service: HistoryService;
let historyRepo: Repository<HistoryEntry>;
let noteRepo: Repository<Note>;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [HistoryService], providers: [
imports: [LoggerModule], HistoryService,
}).compile(); {
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>(HistoryService); service = module.get<HistoryService>(HistoryService);
historyRepo = module.get<Repository<HistoryEntry>>(
getRepositoryToken(HistoryEntry),
);
noteRepo = module.get<Repository<Note>>(getRepositoryToken(Note));
}); });
it('should be defined', () => { it('should be defined', () => {
expect(service).toBeDefined(); 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<HistoryEntry> => 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<HistoryEntry> => 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<HistoryEntry> => 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<HistoryEntry> => {
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);
});
});
});
}); });

View file

@ -10,7 +10,7 @@ import { HistoryEntryUpdateDto } from './history-entry-update.dto';
import { HistoryEntryDto } from './history-entry.dto'; import { HistoryEntryDto } from './history-entry.dto';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { HistoryEntry } from './history-entry.enity'; import { HistoryEntry } from './history-entry.entity';
import { UsersService } from '../users/users.service'; import { UsersService } from '../users/users.service';
import { NotesService } from '../notes/notes.service'; import { NotesService } from '../notes/notes.service';
import { User } from '../users/user.entity'; import { User } from '../users/user.entity';

View file

@ -7,28 +7,68 @@
import { INestApplication } from '@nestjs/common'; import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import * as request from 'supertest'; import * as request from 'supertest';
import { AppModule } from '../../src/app.module';
import { UserInfoDto } from '../../src/users/user-info.dto'; import { UserInfoDto } from '../../src/users/user-info.dto';
import { HistoryService } from '../../src/history/history.service'; import { HistoryService } from '../../src/history/history.service';
import { NotesService } from '../../src/notes/notes.service'; import { NotesService } from '../../src/notes/notes.service';
import { HistoryEntryUpdateDto } from '../../src/history/history-entry-update.dto'; import { HistoryEntryUpdateDto } from '../../src/history/history-entry-update.dto';
import { HistoryEntryDto } from '../../src/history/history-entry.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 // TODO Tests have to be reworked using UserService functions
describe('Notes', () => { describe('Notes', () => {
let app: INestApplication; let app: INestApplication;
//let usersService: UsersService;
let historyService: HistoryService; let historyService: HistoryService;
let notesService: NotesService; let notesService: NotesService;
let user: User;
beforeAll(async () => { beforeAll(async () => {
const moduleRef = await Test.createTestingModule({ const moduleRef = await Test.createTestingModule({
imports: [AppModule], imports: [
}).compile(); ConfigModule.forRoot({
// TODO Create User and generateAPI Token or other Auth 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(); 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(); await app.init();
}); });
@ -42,87 +82,65 @@ describe('Notes', () => {
expect(response.body.content).toEqual(userInfo); expect(response.body.content).toEqual(userInfo);
}); });
it.skip(`GET /me/history`, async () => { it(`GET /me/history`, async () => {
// TODO user has to be chosen const noteName = 'testGetNoteHistory1';
/* TODO Note maybe not added to history by createNote, const note = await notesService.createNote('', noteName);
use function from HistoryService instead const createdHistoryEntry = await historyService.createOrUpdateHistoryEntry(
*/ note,
await notesService.createNote('', 'testGetHistory'); user,
);
const response = await request(app.getHttpServer()) const response = await request(app.getHttpServer())
.get('/me/history') .get('/me/history')
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200); .expect(200);
let historyEntry: HistoryEntryDto; const history = <HistoryEntryDto[]>response.body;
for (const e of <any[]>response.body.content) { for (const historyEntry of history) {
if ((<HistoryEntryDto>e).metadata.alias === 'testGetHistory') { if ((<HistoryEntryDto>historyEntry).identifier === 'testGetHistory') {
historyEntry = e; expect(historyEntry).toEqual(createdHistoryEntry);
} }
} }
expect(historyEntry).toEqual(history);
}); });
it.skip(`GET /me/history/{note}`, async () => { it(`PUT /me/history/{note}`, async () => {
const noteName = 'testGetNoteHistory'; const noteName = 'testGetNoteHistory2';
/* TODO Note maybe not added to history by createNote, const note = await notesService.createNote('', noteName);
use function from HistoryService instead await historyService.createOrUpdateHistoryEntry(note, user);
*/
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);
const historyEntryUpdateDto = new HistoryEntryUpdateDto(); const historyEntryUpdateDto = new HistoryEntryUpdateDto();
historyEntryUpdateDto.pinStatus = true; historyEntryUpdateDto.pinStatus = true;
const response = await request(app.getHttpServer()) const response = await request(app.getHttpServer())
.put('/me/history/' + noteName) .put('/me/history/' + noteName)
.send(historyEntryUpdateDto) .send(historyEntryUpdateDto)
.expect(200); .expect(200);
// TODO parameter is not used for now const history = await historyService.getEntriesByUser(user);
const history = historyService.getEntriesByUser('testuser'); let historyEntry: HistoryEntryDto = response.body;
let historyEntry: HistoryEntryDto;
for (const e of <any[]>response.body.content) {
if ((<HistoryEntryDto>e).metadata.alias === noteName) {
historyEntry = e;
}
}
expect(historyEntry.pinStatus).toEqual(true); expect(historyEntry.pinStatus).toEqual(true);
historyEntry = null; historyEntry = null;
for (const e of history) { for (const e of history) {
if (e.metadata.alias === noteName) { if (e.note.alias === noteName) {
historyEntry = e; historyEntry = await historyService.toHistoryEntryDto(e);
} }
} }
expect(historyEntry.pinStatus).toEqual(true); 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 ((<HistoryEntry>e).note.alias === noteName) {
historyEntry = e;
}
}
return expect(historyEntry).toBeNull();
});
it.skip(`GET /me/notes/`, async () => { it.skip(`GET /me/notes/`, async () => {
// TODO use function from HistoryService to add an History Entry // TODO use function from HistoryService to add an History Entry
await notesService.createNote('This is a test note.', 'test7'); await notesService.createNote('This is a test note.', 'test7');